From 5fabb0e866f00c94f5a0ee8c90ee2b55b5252ad3 Mon Sep 17 00:00:00 2001 From: pagopa-github-bot Date: Wed, 25 Sep 2024 07:39:05 +0000 Subject: [PATCH] [PAGOPA-2168] log optimization: managed logging --- .github/workflows/release_deploy.yml | 27 +- Dockerfile | 32 ++- docker/applicationinsights.json | 19 ++ docker/run_docker.sh | 15 +- helm/Chart.yaml | 4 +- helm/values-dev.yaml | 6 +- helm/values-prod.yaml | 6 +- helm/values-uat.yaml | 6 +- openapi/openapi.json | 2 +- openapi/openapi_ec.json | 2 +- openapi/openapi_helpdesk.json | 2 +- openapi/openapi_io.json | 2 +- openapi/openapi_io_patch.json | 2 +- openapi/openapi_io_patch_lap.json | 2 +- pom.xml | 4 +- .../config/LoggingAspect.java | 247 +++++++++++------- .../bizeventsservice/exception/AppError.java | 4 +- .../bizeventsservice/util/CommonUtil.java | 71 +++++ 18 files changed, 305 insertions(+), 148 deletions(-) create mode 100644 docker/applicationinsights.json create mode 100644 src/main/java/it/gov/pagopa/bizeventsservice/util/CommonUtil.java diff --git a/.github/workflows/release_deploy.yml b/.github/workflows/release_deploy.yml index 171df911..73974732 100644 --- a/.github/workflows/release_deploy.yml +++ b/.github/workflows/release_deploy.yml @@ -16,17 +16,21 @@ on: - dev - uat - prod - - all + version: + required: false + type: choice + description: Select the version + options: + - patch + - skip_or_promote + - new_release + - breaking_change + beta: required: false type: boolean description: deploy beta version on AKS default: false - skip_release: - required: false - type: boolean - description: skip the release. Only deploy - default: false permissions: @@ -58,16 +62,17 @@ jobs: - if: ${{ (github.event.pull_request.merged && contains(github.event.pull_request.labels.*.name, 'breaking-change')) }} run: echo "SEMVER=major" >> $GITHUB_ENV - - if: ${{ inputs.environment == 'uat' }} + # force semver if dev, !=main or skip release + - if: ${{ inputs.version == 'new_release' }} run: echo "SEMVER=minor" >> $GITHUB_ENV - - if: ${{ inputs.environment == 'prod' }} - run: echo "SEMVER=skip" >> $GITHUB_ENV + - if: ${{ inputs.version == 'breaking_change' }} + run: echo "SEMVER=major" >> $GITHUB_ENV - if: ${{ github.ref_name != 'main' }} run: echo "SEMVER=buildNumber" >> $GITHUB_ENV - - if: ${{ inputs.skip_release }} + - if: ${{ inputs.version == 'skip_or_promote' }} run: echo "SEMVER=skip" >> $GITHUB_ENV - id: get_semver @@ -145,4 +150,4 @@ jobs: footer: 'Linked to <{workflow_url}| workflow file>' icon_success: ':white_check_mark:' env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 89078730..0cb58758 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,31 @@ +# +# Build +# FROM maven:3.9.3-amazoncorretto-17@sha256:4ab7db7bd5f95e58b0ba1346ff29d6abdd9b73e5fd89c5140edead8b037386ff AS buildtime - WORKDIR /build COPY . . +RUN mvn clean package -Dmaven.test.skip=true -RUN mvn clean package -DskipTests +# +# Package stage +# +FROM --platform=linux/amd64 amazoncorretto:17.0.9-alpine3.18@sha256:df48bf2e183230040890460ddb4359a10aa6c7aad24bd88899482c52053c7e17 as builder +COPY --from=buildtime /build/target/*.jar application.jar +RUN java -Djarmode=layertools -jar application.jar extract -FROM amazoncorretto:17.0.8-alpine3.18@sha256:0c61f12abfb091be48474e836e6802ff3a93e8e038e0460af8c7f447ccbd3901 AS runtime -VOLUME /tmp -WORKDIR /app +FROM ghcr.io/pagopa/docker-base-springboot-openjdk17:v1.1.0@sha256:6fa320d452fa22066441f1ef292d15eb06f944bc8bca293e1a91ea460d30a613 +#ADD --chown=spring:spring https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.25.1/opentelemetry-javaagent.jar . -COPY --from=buildtime /build/target/*.jar /app/app.jar -# The agent is enabled at runtime via JAVA_TOOL_OPTIONS. -ADD https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.4.15/applicationinsights-agent-3.4.15.jar /app/applicationinsights-agent.jar +COPY --chown=spring:spring --from=builder dependencies/ ./ +COPY --chown=spring:spring --from=builder snapshot-dependencies/ ./ +COPY --chown=spring:spring docker/applicationinsights.json ./applicationinsights.json -RUN chown -R nobody:nobody /app +# https://github.com/moby/moby/issues/37965#issuecomment-426853382 +RUN true +COPY --chown=spring:spring --from=builder spring-boot-loader/ ./ +COPY --chown=spring:spring --from=builder application/ ./ EXPOSE 8080 -# USER 65534 - -ENTRYPOINT [ "java","-jar","/app/app.jar" ] \ No newline at end of file +#ENTRYPOINT ["java","-javaagent:opentelemetry-javaagent.jar","--enable-preview","org.springframework.boot.loader.JarLauncher"] \ No newline at end of file diff --git a/docker/applicationinsights.json b/docker/applicationinsights.json new file mode 100644 index 00000000..312fe75f --- /dev/null +++ b/docker/applicationinsights.json @@ -0,0 +1,19 @@ +{ + "selfDiagnostics": { + "destination": "console", + "level": "INFO" + }, + "sampling": { + "requestsPerSecond": 5 + }, + "preview": { + "sampling": { + "overrides": [ + { + "telemetryKind": "exception", + "percentage": 100 + } + ] + } + } +} \ No newline at end of file diff --git a/docker/run_docker.sh b/docker/run_docker.sh index 1f8e006f..335b113a 100644 --- a/docker/run_docker.sh +++ b/docker/run_docker.sh @@ -28,16 +28,19 @@ fi config=$(yq -r '."microservice-chart".envConfig' ../helm/values-$ENV.yaml) # set word splitting IFS=$'\n' -for line in $(echo $config | jq -r '. | to_entries[] | select(.key) | "\(.key)=\(.value)"'); do - echo $line >> .env +for line in $(echo "$config" | yq -r '. | to_entries[] | select(.key) | "\(.key)=\(.value)"'); do + echo "$line" >> .env done keyvault=$(yq -r '."microservice-chart".keyvault.name' ../helm/values-$ENV.yaml) secret=$(yq -r '."microservice-chart".envSecret' ../helm/values-$ENV.yaml) -for line in $(echo $secret | jq -r '. | to_entries[] | select(.key) | "\(.key)=\(.value)"'); do +for line in $(echo "$secret" | yq -r '. | to_entries[] | select(.key) | "\(.key)=\(.value)"'); do IFS='=' read -r -a array <<< "$line" response=$(az keyvault secret show --vault-name $keyvault --name "${array[1]}") - value=$(echo $response | jq -r '.value') + response=$(echo "$response" | tr -d '\n') + value=$(echo "$response" | yq -r '.value') + value=$(echo "$value" | sed 's/\$/\$\$/g') + value=$(echo "$value" | tr -d '\n') echo "${array[0]}=$value" >> .env done @@ -45,12 +48,10 @@ done stack_name=$(cd .. && basename "$PWD") docker compose -p "${stack_name}" up -d --remove-orphans --force-recreate --build - # waiting the containers printf 'Waiting for the service' attempt_counter=0 max_attempts=50 -# until [ $(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/info) -ne 200 ]; do until $(curl --output /dev/null --silent --head --fail http://localhost:8080/info); do if [ ${attempt_counter} -eq ${max_attempts} ];then echo "Max attempts reached" @@ -61,4 +62,4 @@ until $(curl --output /dev/null --silent --head --fail http://localhost:8080/inf attempt_counter=$((attempt_counter+1)) sleep 5 done -echo 'Service Started' +echo 'Service Started' \ No newline at end of file diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 5f62f2b8..95a31ff9 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: pagopa-biz-events-service description: Microservice for exposing REST APIs about payment receipts. type: application -version: 0.83.0 -appVersion: 0.1.58 +version: 0.84.0 +appVersion: 0.1.59 dependencies: - name: microservice-chart version: 2.4.0 diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index a5c39d91..bf3b4f86 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -4,7 +4,7 @@ microservice-chart: fullnameOverride: "" image: repository: ghcr.io/pagopa/pagopa-biz-events-service - tag: "0.1.58" + tag: "0.1.59" pullPolicy: Always livenessProbe: httpGet: @@ -88,8 +88,8 @@ microservice-chart: CONNECTION_TIMEOUT: "10000" REDIS_PORT: "6380" REDIS_TTL: "5" # 5 minutes - OTEL_SERVICE_NAME: "pagopabizeventsservice" - OTEL_RESOURCE_ATTRIBUTES: "service.name=pagopareceiptspdfserviceotl,deployment.environment=dev" + OTEL_SERVICE_NAME: "pagopa-bizevents-service" + OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=dev" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index c17acb5b..223310a7 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -4,7 +4,7 @@ microservice-chart: fullnameOverride: "" image: repository: ghcr.io/pagopa/pagopa-biz-events-service - tag: "0.1.58" + tag: "0.1.59" pullPolicy: Always livenessProbe: httpGet: @@ -88,8 +88,8 @@ microservice-chart: CONNECTION_TIMEOUT: "10000" REDIS_PORT: "6380" REDIS_TTL: "20" # 20 minutes - OTEL_SERVICE_NAME: "pagopabizeventsservice" - OTEL_RESOURCE_ATTRIBUTES: "service.name=pagopareceiptspdfserviceotl,deployment.environment=prod" + OTEL_SERVICE_NAME: "pagopa-bizevents-service" + OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=prod" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index f103f924..68e96dea 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -4,7 +4,7 @@ microservice-chart: fullnameOverride: "" image: repository: ghcr.io/pagopa/pagopa-biz-events-service - tag: "0.1.58" + tag: "0.1.59" pullPolicy: Always livenessProbe: httpGet: @@ -88,8 +88,8 @@ microservice-chart: CONNECTION_TIMEOUT: "10000" REDIS_PORT: "6380" REDIS_TTL: "20" # 20 minutes - OTEL_SERVICE_NAME: "pagopabizeventsservice" - OTEL_RESOURCE_ATTRIBUTES: "service.name=pagopareceiptspdfserviceotl,deployment.environment=uat" + OTEL_SERVICE_NAME: "pagopa-bizevents-service" + OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=uat" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" diff --git a/openapi/openapi.json b/openapi/openapi.json index 01ba84d4..f4c6541e 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -4,7 +4,7 @@ "title": "Biz-Events Service", "description": "Microservice for exposing REST APIs about payment receipts.", "termsOfService": "https://www.pagopa.gov.it/", - "version": "0.1.58" + "version": "0.1.59" }, "servers": [ { diff --git a/openapi/openapi_ec.json b/openapi/openapi_ec.json index cb95058c..48c84a8c 100644 --- a/openapi/openapi_ec.json +++ b/openapi/openapi_ec.json @@ -4,7 +4,7 @@ "title": "Biz-Events Service", "description": "Microservice for exposing REST APIs about payment receipts.", "termsOfService": "https://www.pagopa.gov.it/", - "version": "0.1.58" + "version": "0.1.59" }, "servers": [ { diff --git a/openapi/openapi_helpdesk.json b/openapi/openapi_helpdesk.json index fc10ec94..cc1bb79b 100644 --- a/openapi/openapi_helpdesk.json +++ b/openapi/openapi_helpdesk.json @@ -4,7 +4,7 @@ "title": "Biz-Events Service", "description": "Microservice for exposing REST APIs about payment receipts.", "termsOfService": "https://www.pagopa.gov.it/", - "version": "0.1.58" + "version": "0.1.59" }, "servers": [ { diff --git a/openapi/openapi_io.json b/openapi/openapi_io.json index 8256594b..e28cd158 100644 --- a/openapi/openapi_io.json +++ b/openapi/openapi_io.json @@ -4,7 +4,7 @@ "title": "Biz-Events Service", "description": "Microservice for exposing REST APIs about payment receipts.", "termsOfService": "https://www.pagopa.gov.it/", - "version": "0.1.58" + "version": "0.1.59" }, "servers": [ { diff --git a/openapi/openapi_io_patch.json b/openapi/openapi_io_patch.json index 5e134171..ce534011 100644 --- a/openapi/openapi_io_patch.json +++ b/openapi/openapi_io_patch.json @@ -4,7 +4,7 @@ "title": "Biz-Events Service", "description": "Microservice for exposing REST APIs about payment receipts.", "termsOfService": "https://www.pagopa.gov.it/", - "version": "0.1.58" + "version": "0.1.59" }, "servers": [ { diff --git a/openapi/openapi_io_patch_lap.json b/openapi/openapi_io_patch_lap.json index 8860b204..7d369b58 100644 --- a/openapi/openapi_io_patch_lap.json +++ b/openapi/openapi_io_patch_lap.json @@ -4,7 +4,7 @@ "title": "Biz-Events Service", "description": "Microservice for exposing REST APIs about payment receipts.", "termsOfService": "https://www.pagopa.gov.it/", - "version": "0.1.58" + "version": "0.1.59" }, "servers": [ { diff --git a/pom.xml b/pom.xml index 36c67588..cd0cd353 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ it.gov.pagopa bizeventsservice - 0.1.58 + 0.1.59 Biz-Events Service Microservice for exposing REST APIs about payment receipts. @@ -20,7 +20,7 @@ 1.6.12 3.1.0 3.29.1 - 1.5.0 + 1.6.0 2021.0.9 diff --git a/src/main/java/it/gov/pagopa/bizeventsservice/config/LoggingAspect.java b/src/main/java/it/gov/pagopa/bizeventsservice/config/LoggingAspect.java index 627dcdfc..ba4e5a26 100644 --- a/src/main/java/it/gov/pagopa/bizeventsservice/config/LoggingAspect.java +++ b/src/main/java/it/gov/pagopa/bizeventsservice/config/LoggingAspect.java @@ -1,112 +1,163 @@ package it.gov.pagopa.bizeventsservice.config; -import lombok.extern.slf4j.Slf4j; +import static it.gov.pagopa.bizeventsservice.util.CommonUtil.deNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.*; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.CodeSignature; +import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.EventListener; -import org.springframework.core.env.AbstractEnvironment; -import org.springframework.core.env.EnumerablePropertySource; -import org.springframework.core.env.Environment; -import org.springframework.core.env.MutablePropertySources; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; -import java.util.Arrays; -import java.util.stream.StreamSupport; +import it.gov.pagopa.bizeventsservice.exception.AppError; +import it.gov.pagopa.bizeventsservice.model.ProblemJson; +import lombok.extern.slf4j.Slf4j;; @Aspect @Component @Slf4j public class LoggingAspect { - @Value("${info.application.artifactId}") - private String artifactId; - - @Value("${info.application.version}") - private String version; - - @Value("${info.properties.environment}") - private String environment; - - @Pointcut("@within(org.springframework.web.bind.annotation.RestController)") - public void restController() { - // all rest controllers - } - - @Pointcut("execution(* it.gov.pagopa.bizeventsservice.repository..*.*(..))") - public void repository() { - // all repository methods - } - - @Pointcut("execution(* it.gov.pagopa.bizeventsservice.service..*.*(..))") - public void service() { - // all service methods - } - - @Pointcut("execution(* it.gov.pagopa.bizeventsservice.exception.ErrorHandler.*(..))") - public void errorHandler() { - // all service methods - } - - /** - * Log essential info of application during the startup. - */ - @PostConstruct - public void logStartup() { - log.info("-> Starting {} version {} - environment {}", artifactId, version, environment); - } - - /** - * If DEBUG log-level is enabled prints the env variables and the application properties. - * - * @param event Context of application - */ - @EventListener - public void handleContextRefresh(ContextRefreshedEvent event) { - final Environment env = event.getApplicationContext().getEnvironment(); - log.debug("Active profiles: {}", Arrays.toString(env.getActiveProfiles())); - final MutablePropertySources sources = ((AbstractEnvironment) env).getPropertySources(); - StreamSupport.stream(sources.spliterator(), false) - .filter(EnumerablePropertySource.class::isInstance) - .map(ps -> ((EnumerablePropertySource) ps).getPropertyNames()) - .flatMap(Arrays::stream) - .distinct() - .filter(prop -> !(prop.toLowerCase().contains("credentials") || prop.toLowerCase().contains("password") || prop.toLowerCase().contains("pass") || prop.toLowerCase().contains("pwd"))) - .forEach(prop -> log.debug("{}: {}", prop, env.getProperty(prop))); - } - - @Before(value = "restController()") - public void logApiInvocation(JoinPoint joinPoint) { - log.info("Invoking API operation {} - args: {}", joinPoint.getSignature().getName(), joinPoint.getArgs()); - } - - @AfterReturning(value = "restController()", returning = "result") - public void returnApiInvocation(JoinPoint joinPoint, Object result) { - log.info("Successful API operation {} - result: {}", joinPoint.getSignature().getName(), result); - } - - @AfterReturning(value = "errorHandler()", returning = "result") - public void trowingApiInvocation(JoinPoint joinPoint, Object result) { - log.info("Failed API operation {} - error: {}", joinPoint.getSignature().getName(), result); - } - - @Around(value = "repository() || service()") - public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { - long startTime = System.currentTimeMillis(); - Object result = joinPoint.proceed(); - long endTime = System.currentTimeMillis(); - log.trace("Time taken for Execution of {} is: {}ms", joinPoint.getSignature().toShortString(), (endTime - startTime)); - return result; - } - - @Around(value = "repository() || service()") - public Object logTrace(ProceedingJoinPoint joinPoint) throws Throwable { - log.debug("Call method {} - args: {}", joinPoint.getSignature().toShortString(), joinPoint.getArgs()); - Object result = joinPoint.proceed(); - log.debug("Return method {} - result: {}", joinPoint.getSignature().toShortString(), result); - return result; - } + public static final String START_TIME = "startTime"; + public static final String METHOD = "method"; + public static final String STATUS = "status"; + public static final String CODE = "httpCode"; + public static final String RESPONSE_TIME = "responseTime"; + public static final String FAULT_CODE = "faultCode"; + public static final String FAULT_DETAIL = "faultDetail"; + public static final String REQUEST_ID = "requestId"; + public static final String OPERATION_ID = "operationId"; + public static final String ARGS = "args"; + + final HttpServletRequest httRequest; + + final HttpServletResponse httpResponse; + + @Value("${info.application.name}") + private String name; + + @Value("${info.application.version}") + private String version; + + @Value("${info.properties.environment}") + private String environment; + + public LoggingAspect(HttpServletRequest httRequest, HttpServletResponse httpResponse) { + this.httRequest = httRequest; + this.httpResponse = httpResponse; + } + + private static String getDetail(ResponseEntity result) { + if (result != null && result.getBody() != null && result.getBody().getDetail() != null) { + return result.getBody().getDetail(); + } else return AppError.UNKNOWN.getDetails(); + } + + private static String getTitle(ResponseEntity result) { + if (result != null && result.getBody() != null && result.getBody().getTitle() != null) { + return result.getBody().getTitle(); + } else return AppError.UNKNOWN.getTitle(); + } + + public static String getExecutionTime() { + String startTime = MDC.get(START_TIME); + if (startTime != null) { + long endTime = System.currentTimeMillis(); + long executionTime = endTime - Long.parseLong(startTime); + return String.valueOf(executionTime); + } + return "-"; + } + + private static Map getParams(ProceedingJoinPoint joinPoint) { + CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature(); + Map params = new HashMap<>(); + int i = 0; + for (var paramName : codeSignature.getParameterNames()) { + params.put(paramName, deNull(joinPoint.getArgs()[i++])); + } + return params; + } + + @Pointcut("@within(org.springframework.web.bind.annotation.RestController)") + public void restController() { + // all rest controllers + } + + @Pointcut("@within(org.springframework.stereotype.Repository)") + public void repository() { + // all repository methods + } + + @Pointcut("@within(org.springframework.stereotype.Service)") + public void service() { + // all service methods + } + + /** Log essential info of application during the startup. */ + @PostConstruct + public void logStartup() { + log.info("-> Starting {} version {} - environment {}", name, version, environment); + } + + @Around(value = "restController()") + public Object logApiInvocation(ProceedingJoinPoint joinPoint) throws Throwable { + MDC.put(METHOD, joinPoint.getSignature().getName()); + MDC.put(START_TIME, String.valueOf(System.currentTimeMillis())); + MDC.put(OPERATION_ID, UUID.randomUUID().toString()); + if (MDC.get(REQUEST_ID) == null) { + var requestId = UUID.randomUUID().toString(); + MDC.put(REQUEST_ID, requestId); + } + Map params = getParams(joinPoint); + MDC.put(ARGS, params.toString()); + + log.info("Invoking API operation {} - args: {}", joinPoint.getSignature().getName(), params); + + Object result = joinPoint.proceed(); + + MDC.put(STATUS, "OK"); + MDC.put(CODE, String.valueOf(httpResponse.getStatus())); + MDC.put(RESPONSE_TIME, getExecutionTime()); + log.info( + "Successful API operation {} - result: {}", joinPoint.getSignature().getName(), result); + MDC.remove(STATUS); + MDC.remove(CODE); + MDC.remove(RESPONSE_TIME); + MDC.remove(START_TIME); + return result; + } + + @AfterReturning(value = "execution(* *..exception.ErrorHandler.*(..))", returning = "result") + public void trowingApiInvocation(JoinPoint joinPoint, ResponseEntity result) { + MDC.put(STATUS, "KO"); + MDC.put(CODE, String.valueOf(result.getStatusCode().value())); + MDC.put(RESPONSE_TIME, getExecutionTime()); + MDC.put(FAULT_CODE, getTitle(result)); + MDC.put(FAULT_DETAIL, getDetail(result)); + log.info("Failed API operation {} - error: {}", MDC.get(METHOD), result); + MDC.clear(); + } + + @Around(value = "repository() || service()") + public Object logTrace(ProceedingJoinPoint joinPoint) throws Throwable { + Map params = getParams(joinPoint); + log.debug("Call method {} - args: {}", joinPoint.getSignature().toShortString(), params); + Object result = joinPoint.proceed(); + log.debug("Return method {} - result: {}", joinPoint.getSignature().toShortString(), result); + return result; + } } diff --git a/src/main/java/it/gov/pagopa/bizeventsservice/exception/AppError.java b/src/main/java/it/gov/pagopa/bizeventsservice/exception/AppError.java index 7b62b8cd..2a4d6128 100644 --- a/src/main/java/it/gov/pagopa/bizeventsservice/exception/AppError.java +++ b/src/main/java/it/gov/pagopa/bizeventsservice/exception/AppError.java @@ -25,7 +25,9 @@ public enum AppError { VIEW_CART_NOT_FOUND_WITH_TRANSACTION_ID_AND_TAX_CODE(HttpStatus.NOT_FOUND, VIEW_CART_NOT_FOUND, "Not found a biz-events-view-cart with id %s for the given tax code"), ERROR_MAPPING_BIZ_EVENT_TO_TRANSACTION_DETAIL(HttpStatus.INTERNAL_SERVER_ERROR, INVALID_DATA, "Error mapping bizEvent data to transaction details, missing property %s for bizEvent with id %s"), - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error", "Something was wrong"); + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error", "Something was wrong"), + + UNKNOWN(null, null, null); public final HttpStatus httpStatus; public final String title; diff --git a/src/main/java/it/gov/pagopa/bizeventsservice/util/CommonUtil.java b/src/main/java/it/gov/pagopa/bizeventsservice/util/CommonUtil.java new file mode 100644 index 00000000..4a485357 --- /dev/null +++ b/src/main/java/it/gov/pagopa/bizeventsservice/util/CommonUtil.java @@ -0,0 +1,71 @@ +package it.gov.pagopa.bizeventsservice.util; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Optional; + +import javax.xml.XMLConstants; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.stax.StAXSource; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; + +import lombok.experimental.UtilityClass; +import org.xml.sax.SAXException; + +@UtilityClass +public class CommonUtil { + /** + * @param xml file XML to validate + * @param xsdUrl url of XSD + * @throws SAXException if XML is not valid + * @throws IOException if XSD schema not found + * @throws XMLStreamException error during read XML + */ + public static void syntacticValidationXml(byte[] xml, File xsdUrl) + throws SAXException, IOException, XMLStreamException { + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + // to be compliant, prohibit the use of all protocols by external entities: + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + javax.xml.validation.Schema schema = factory.newSchema(xsdUrl); + Validator validator = schema.newValidator(); + + XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + // to be compliant, completely disable DOCTYPE declaration: + xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); + + XMLStreamReader xmlStreamReader = + xmlInputFactory.createXMLStreamReader(new ByteArrayInputStream(xml)); + StAXSource source = new StAXSource(xmlStreamReader); + validator.validate(source); + } + + public static GregorianCalendar convertToGregorianCalendar(LocalDateTime dateToConvert) { + Date date = Date.from(dateToConvert.atZone(ZoneId.systemDefault()).toInstant()); + GregorianCalendar gregorianCalendar = new GregorianCalendar(); + gregorianCalendar.setTime(date); + + return gregorianCalendar; + } + + public static String sanitizeInput(String input) { + return input != null ? input.replace("\n", "").replaceAll("[^a-zA-Z0-9_\\-+/]", "") : ""; + } + + /** + * @param value value to deNullify. + * @return return empty string if value is null + */ + public static String deNull(Object value) { + return Optional.ofNullable(value).orElse("").toString(); + } +} \ No newline at end of file