diff --git a/backend/Dockerfile b/backend/Dockerfile index ff87c55bd7..40e635565a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -35,4 +35,4 @@ EXPOSE ${PORT} HEALTHCHECK CMD curl -f http://localhost:${PORT}/actuator/health | grep '"status":"UP"' # Startup -ENTRYPOINT ["/app/nr-forest-client-backend"] \ No newline at end of file +ENTRYPOINT ["/app/nr-forest-client-backend","--spring.profiles.active=container"] \ No newline at end of file diff --git a/backend/pom.xml b/backend/pom.xml index f6dab5a3b8..5f602f5e58 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -75,10 +75,25 @@ org.springframework.boot spring-boot-starter-aop + io.micrometer micrometer-registry-prometheus + + io.micrometer + micrometer-tracing-bridge-otel + + + io.opentelemetry + opentelemetry-exporter-zipkin + + + org.springframework.experimental + r2dbc-micrometer-spring-boot + 1.0.2 + + org.springframework spring-jdbc @@ -96,6 +111,7 @@ org.flywaydb flyway-core + org.apache.commons commons-lang3 @@ -110,6 +126,7 @@ lombok true + org.springframework.boot spring-boot-starter-test @@ -172,6 +189,20 @@ reactor-netty-http 1.1.13 + + io.micrometer + micrometer-bom + ${micrometer.version} + pom + import + + + io.micrometer + micrometer-tracing-bom + ${micrometer-tracing.version} + pom + import + @@ -203,10 +234,10 @@ -H:+ReportExceptionStackTraces - --trace-class-initialization=org.apache.commons.logging.LogFactoryService - --trace-class-initialization=org.apache.commons.logging.LogFactory + --trace-class-initialization=org.apache.commons.logging.LogFactoryService,io.micrometer.common.util.internal.logging.LocationAwareSlf4JLogger,ch.qos.logback.classic.Logger + --trace-class-initialization=org.apache.commons.logging.LogFactory,io.micrometer.common.util.internal.logging.LocationAwareSlf4JLogger,ch.qos.logback.classic.Logger - --initialize-at-build-time=org.apache.commons.logging.LogFactoryService + --initialize-at-build-time=org.apache.commons.logging.LogFactoryService,io.micrometer.common.util.internal.logging.LocationAwareSlf4JLogger,ch.qos.logback.classic.Logger diff --git a/backend/src/main/java/ca/bc/gov/app/BootApplication.java b/backend/src/main/java/ca/bc/gov/app/BootApplication.java index 0d9392925f..d2eef3b941 100644 --- a/backend/src/main/java/ca/bc/gov/app/BootApplication.java +++ b/backend/src/main/java/ca/bc/gov/app/BootApplication.java @@ -1,14 +1,19 @@ package ca.bc.gov.app; +import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; +import reactor.core.publisher.Hooks; @SpringBootApplication @EnableScheduling public class BootApplication { public static void main(String[] args) { + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + Hooks.enableAutomaticContextPropagation(); SpringApplication.run(BootApplication.class, args); } diff --git a/backend/src/main/java/ca/bc/gov/app/configuration/TracingConfiguration.java b/backend/src/main/java/ca/bc/gov/app/configuration/TracingConfiguration.java new file mode 100644 index 0000000000..1683becb6e --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/app/configuration/TracingConfiguration.java @@ -0,0 +1,28 @@ +package ca.bc.gov.app.configuration; + +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservedAspect; +import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; +import io.micrometer.tracing.annotation.MethodInvocationProcessor; +import io.micrometer.tracing.annotation.SpanAspect; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@Slf4j +public class TracingConfiguration { + + @Bean + ObservedAspect observedAspect( + ObservationRegistry observationRegistry + ) { + ObservationThreadLocalAccessor.getInstance().setObservationRegistry(observationRegistry); + return new ObservedAspect(observationRegistry); + } + + @Bean + SpanAspect spanAspect(MethodInvocationProcessor methodInvocationProcessor) { + return new SpanAspect(methodInvocationProcessor); + } +} diff --git a/backend/src/main/java/ca/bc/gov/app/controller/GlobalErrorController.java b/backend/src/main/java/ca/bc/gov/app/controller/GlobalErrorController.java index 7a94133ca9..4ab150998a 100644 --- a/backend/src/main/java/ca/bc/gov/app/controller/GlobalErrorController.java +++ b/backend/src/main/java/ca/bc/gov/app/controller/GlobalErrorController.java @@ -1,6 +1,7 @@ package ca.bc.gov.app.controller; import ca.bc.gov.app.exception.ValidationException; +import io.micrometer.observation.annotation.Observed; import java.util.Arrays; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.BooleanUtils; @@ -31,6 +32,7 @@ @Slf4j @Component @Order(-2) +@Observed public class GlobalErrorController extends AbstractErrorWebExceptionHandler { public GlobalErrorController( diff --git a/backend/src/main/java/ca/bc/gov/app/controller/ches/ChesController.java b/backend/src/main/java/ca/bc/gov/app/controller/ches/ChesController.java index deab97128d..c7bcf07ff5 100644 --- a/backend/src/main/java/ca/bc/gov/app/controller/ches/ChesController.java +++ b/backend/src/main/java/ca/bc/gov/app/controller/ches/ChesController.java @@ -2,6 +2,7 @@ import ca.bc.gov.app.dto.client.EmailRequestDto; import ca.bc.gov.app.service.client.ClientService; +import io.micrometer.observation.annotation.Observed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -21,6 +22,7 @@ @Slf4j @RequestMapping(value = "/api/ches", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor +@Observed public class ChesController { private final ClientService clientService; diff --git a/backend/src/main/java/ca/bc/gov/app/controller/client/ClientAddressController.java b/backend/src/main/java/ca/bc/gov/app/controller/client/ClientAddressController.java index b495bd79a0..400839a84f 100644 --- a/backend/src/main/java/ca/bc/gov/app/controller/client/ClientAddressController.java +++ b/backend/src/main/java/ca/bc/gov/app/controller/client/ClientAddressController.java @@ -3,6 +3,7 @@ import ca.bc.gov.app.dto.client.ClientAddressDto; import ca.bc.gov.app.dto.client.CodeNameDto; import ca.bc.gov.app.service.client.ClientAddressService; +import io.micrometer.observation.annotation.Observed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; @@ -18,6 +19,7 @@ @Slf4j @RequestMapping(value = "/api/clients", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor +@Observed public class ClientAddressController { private final ClientAddressService clientAddressService; @@ -30,6 +32,8 @@ public Flux findPossibleAddresses( Integer maxSuggestions, @RequestParam(value = "searchTerm", required = true) String searchTerm) { + log.info("Requesting possible addresses for country: {}, maxSuggestions: {}, searchTerm: {}", + country, maxSuggestions, searchTerm); return clientAddressService .findPossibleAddresses(country, maxSuggestions, searchTerm); } @@ -37,6 +41,7 @@ public Flux findPossibleAddresses( @GetMapping("/addresses/{addressId}") public Mono getAddress( @PathVariable String addressId) { + log.info("Requesting address for addressId: {}", addressId); return clientAddressService .getAddress(addressId); } diff --git a/backend/src/main/java/ca/bc/gov/app/controller/client/ClientController.java b/backend/src/main/java/ca/bc/gov/app/controller/client/ClientController.java index 286fe895eb..cd45349009 100644 --- a/backend/src/main/java/ca/bc/gov/app/controller/client/ClientController.java +++ b/backend/src/main/java/ca/bc/gov/app/controller/client/ClientController.java @@ -7,6 +7,7 @@ import ca.bc.gov.app.dto.client.EmailRequestDto; import ca.bc.gov.app.exception.NoClientDataFound; import ca.bc.gov.app.service.client.ClientService; +import io.micrometer.observation.annotation.Observed; import java.time.LocalDate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -30,6 +31,7 @@ @RequestMapping(value = "/api/clients", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor @Slf4j +@Observed public class ClientController { private final ClientService clientService; @@ -40,6 +42,7 @@ public Mono getClientDetails( @RequestHeader(ApplicationConstant.USERID_HEADER) String userId, @RequestHeader(name = ApplicationConstant.BUSINESSID_HEADER, defaultValue = StringUtils.EMPTY) String businessId ) { + log.info("Requesting client details for client number {} from the client service.", clientNumber); return clientService.getClientDetails(clientNumber,userId,businessId); } @@ -48,7 +51,9 @@ public Flux listCountries( @RequestParam(value = "page", required = false, defaultValue = "0") Integer page, @RequestParam(value = "size", required = false, defaultValue = "10") - Integer size) { + Integer size + ) { + log.info("Requesting a list of countries from the client service."); return clientService .listCountries(page, size); } @@ -56,6 +61,7 @@ public Flux listCountries( @GetMapping("/getCountryByCode/{countryCode}") public Mono getCountryByCode( @PathVariable String countryCode) { + log.info("Requesting a country by code {} from the client service.", countryCode); return clientService.getCountryByCode(countryCode); } @@ -66,6 +72,7 @@ public Flux listProvinces( Integer page, @RequestParam(value = "size", required = false, defaultValue = "10") Integer size) { + log.info("Requesting a list of provinces for country code {} from the client service.", countryCode); return clientService .listProvinces(countryCode, page, size); } @@ -73,11 +80,13 @@ public Flux listProvinces( @GetMapping("/getClientTypeByCode/{code}") public Mono getClientTypeByCode( @PathVariable String code) { + log.info("Requesting a client type by code {} from the client service.", code); return clientService.getClientTypeByCode(code); } @GetMapping("/activeClientTypeCodes") public Flux findActiveClientTypeCodes() { + log.info("Requesting a list of active client type codes from the client service."); return clientService .findActiveClientTypeCodes(LocalDate.now()); } @@ -89,6 +98,7 @@ public Flux listClientContactTypeCodes( @RequestParam(value = "size", required = false, defaultValue = "10") Integer size ) { + log.info("Requesting a list of active client contact type codes from the client service."); return clientService .listClientContactTypeCodes(LocalDate.now(),page, size); } @@ -103,6 +113,7 @@ public Flux listClientContactTypeCodes( public Flux findByClientName( @PathVariable String name ) { + log.info("Requesting a list of clients with name {} from the client service.", name); return clientService .findByClientNameOrIncorporation(name) .map(client -> client.withName(WordUtils.capitalize(client.name()))); @@ -111,6 +122,7 @@ public Flux findByClientName( @GetMapping(value = "/incorporation/{incorporationId}") public Mono findByIncorporationNumber( @PathVariable String incorporationId) { + log.info("Requesting a client with incorporation number {} from the client service.", incorporationId); return clientService .findByClientNameOrIncorporation(incorporationId) .next() @@ -133,6 +145,7 @@ public Mono sendEmail( @RequestHeader(ApplicationConstant.USERID_HEADER) String userId, @RequestHeader(name = ApplicationConstant.BUSINESSID_HEADER, defaultValue = StringUtils.EMPTY) String businessId ) { + log.info("Sending email to {} from the client service.", emailRequestDto.email()); return clientService.triggerEmailDuplicatedClient(emailRequestDto, userId, businessId); } diff --git a/backend/src/main/java/ca/bc/gov/app/controller/client/ClientSubmissionController.java b/backend/src/main/java/ca/bc/gov/app/controller/client/ClientSubmissionController.java index 083ae38999..7a0a79cf5a 100644 --- a/backend/src/main/java/ca/bc/gov/app/controller/client/ClientSubmissionController.java +++ b/backend/src/main/java/ca/bc/gov/app/controller/client/ClientSubmissionController.java @@ -11,8 +11,10 @@ import ca.bc.gov.app.models.client.SubmissionStatusEnum; import ca.bc.gov.app.service.client.ClientSubmissionService; import ca.bc.gov.app.validator.client.ClientSubmitRequestValidator; +import io.micrometer.observation.annotation.Observed; import java.util.List; import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -32,6 +34,8 @@ @RestController @RequestMapping(value = "/api/clients/submissions", produces = MediaType.APPLICATION_JSON_VALUE) +@Observed +@Slf4j public class ClientSubmissionController extends AbstractController { @@ -61,6 +65,8 @@ public Flux listSubmissions( @RequestParam(required = false) String[] updatedAt ) { + log.info("Listing submissions: page={}, size={}, requestType={}, requestStatus={}, clientType={}, name={}, updatedAt={}", + page, size, requestType, requestStatus, clientType, name, updatedAt); return clientService .listSubmissions( page, @@ -82,17 +88,23 @@ public Mono submit( @RequestHeader(ApplicationConstant.USERMAIL_HEADER) String userEmail, @RequestHeader(ApplicationConstant.USERNAME_HEADER) String userName, ServerHttpResponse serverResponse) { - return Mono.just(request) + + return Mono + .just(request) .switchIfEmpty( Mono.error(new InvalidRequestObjectException("no request body was provided")) ) + .doOnNext(sub -> log.info("Submitting request: {}", sub)) .doOnNext(this::validate) + .doOnNext(sub -> log.info("Request is valid: {}", sub)) + .doOnError(e -> log.error("Request is invalid: {}", e.getMessage())) .flatMap(submissionDto -> clientService.submit( submissionDto, userId, userEmail, userName, businessId)) + .doOnNext(submissionId -> log.info("Submission persisted: {}", submissionId)) .doOnNext(submissionId -> serverResponse .getHeaders() diff --git a/backend/src/main/java/ca/bc/gov/app/controller/cognito/CognitoController.java b/backend/src/main/java/ca/bc/gov/app/controller/cognito/CognitoController.java index fa85523d38..306c5faf66 100644 --- a/backend/src/main/java/ca/bc/gov/app/controller/cognito/CognitoController.java +++ b/backend/src/main/java/ca/bc/gov/app/controller/cognito/CognitoController.java @@ -4,6 +4,7 @@ import ca.bc.gov.app.configuration.ForestClientConfiguration; import ca.bc.gov.app.exception.UnableToProcessRequestException; import ca.bc.gov.app.service.cognito.CognitoService; +import io.micrometer.observation.annotation.Observed; import java.time.Duration; import java.util.UUID; import java.util.stream.Stream; @@ -27,6 +28,7 @@ @RestController @Slf4j @RequiredArgsConstructor +@Observed public class CognitoController { public static final String LOCATION = "Location"; @@ -76,8 +78,11 @@ public Mono logon( .getHeaders() .add(LOCATION, famUrl); + log.info("Executing login for provider: {}", code); + return Mono.empty(); } else { + log.error("Invalid provider code: {}", code); return Mono.error(new UnableToProcessRequestException("Invalid provider code.")); } } @@ -119,6 +124,8 @@ public Mono logout( .getHeaders() .add(LOCATION, famUrl); + log.info("Executing logout"); + return Mono.empty(); } @@ -126,6 +133,8 @@ public Mono logout( @ResponseStatus(HttpStatus.FOUND) public Mono refresh(@RequestParam String code, ServerHttpResponse serverResponse) { + log.info("Executing refresh for code: {}", code); + return service .refreshToken(code) @@ -164,6 +173,8 @@ public Mono extractToken( ServerHttpResponse serverResponse ) { + log.info("Extracting JWT from code: {}", code); + return Mono .just(code) diff --git a/backend/src/main/java/ca/bc/gov/app/service/bcregistry/BcRegistryService.java b/backend/src/main/java/ca/bc/gov/app/service/bcregistry/BcRegistryService.java index 74c897b2d9..41ade0ee42 100644 --- a/backend/src/main/java/ca/bc/gov/app/service/bcregistry/BcRegistryService.java +++ b/backend/src/main/java/ca/bc/gov/app/service/bcregistry/BcRegistryService.java @@ -15,6 +15,7 @@ import ca.bc.gov.app.dto.bcregistry.BcRegistryOfficesDto; import ca.bc.gov.app.exception.InvalidAccessTokenException; import ca.bc.gov.app.exception.NoClientDataFound; +import io.micrometer.observation.annotation.Observed; import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; @@ -29,6 +30,7 @@ @Slf4j @Service +@Observed public class BcRegistryService { private final WebClient bcRegistryApi; @@ -37,7 +39,6 @@ public BcRegistryService(@Qualifier("bcRegistryApi") WebClient bcRegistryApi) { this.bcRegistryApi = bcRegistryApi; } - /** * Searches the BC Registry API for {@link BcRegistryFacetSearchResultEntryDto} instances matching * the given value. diff --git a/backend/src/main/java/ca/bc/gov/app/service/ches/ChesService.java b/backend/src/main/java/ca/bc/gov/app/service/ches/ChesService.java index 7e5ad922b1..40da8f8937 100644 --- a/backend/src/main/java/ca/bc/gov/app/service/ches/ChesService.java +++ b/backend/src/main/java/ca/bc/gov/app/service/ches/ChesService.java @@ -21,6 +21,8 @@ import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; +import io.micrometer.observation.annotation.Observed; +import io.r2dbc.postgresql.codec.Json; import java.io.IOException; import java.io.StringWriter; import java.time.LocalDateTime; @@ -43,6 +45,7 @@ @Service @Slf4j +@Observed public class ChesService { public static final String FAILED_TO_SEND_EMAIL = "Failed to send email: {}"; @@ -91,6 +94,8 @@ public Mono sendEmail(String templateName, ? subject : String.format("[%s] %s", configuration.getCognito().getEnvironment(), subject); + log.info("Sending email to {} with subject {}", emailAddress, processedSubject); + return this .buildTemplate(templateName, variables) .map(body -> new ChesRequestDto(List.of(emailAddress), body)) @@ -132,6 +137,7 @@ public Mono sendEmail(String templateName, } private Mono saveEmailLog(EmailLogDto emailLogDto, String transactionMsg) { + log.info("Saving email log {}", emailLogDto.emailId()); if (emailLogDto.emailLogId() != null) { return emailLogRepository.findById(emailLogDto.emailLogId()) .flatMap(existingLogEntity -> updateExistingLogEntity( diff --git a/backend/src/main/java/ca/bc/gov/app/service/client/ClientAddressService.java b/backend/src/main/java/ca/bc/gov/app/service/client/ClientAddressService.java index bd29b97347..e74030c252 100644 --- a/backend/src/main/java/ca/bc/gov/app/service/client/ClientAddressService.java +++ b/backend/src/main/java/ca/bc/gov/app/service/client/ClientAddressService.java @@ -8,6 +8,7 @@ import ca.bc.gov.app.dto.client.ClientValueTextDto; import ca.bc.gov.app.dto.client.CodeNameDto; import ca.bc.gov.app.exception.AddressLookupException; +import io.micrometer.observation.annotation.Observed; import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; @@ -22,6 +23,7 @@ @Service @Slf4j +@Observed public class ClientAddressService { private final ForestClientConfiguration.AddressCompleteConfiguration configuration; diff --git a/backend/src/main/java/ca/bc/gov/app/service/client/ClientLegacyService.java b/backend/src/main/java/ca/bc/gov/app/service/client/ClientLegacyService.java index da702a38f8..439a6dc811 100644 --- a/backend/src/main/java/ca/bc/gov/app/service/client/ClientLegacyService.java +++ b/backend/src/main/java/ca/bc/gov/app/service/client/ClientLegacyService.java @@ -1,6 +1,7 @@ package ca.bc.gov.app.service.client; import ca.bc.gov.app.dto.legacy.ForestClientDto; +import io.micrometer.observation.annotation.Observed; import java.util.Map; import java.util.Optional; import lombok.extern.slf4j.Slf4j; @@ -12,6 +13,7 @@ @Slf4j @Service +@Observed public class ClientLegacyService { private final WebClient legacyApi; diff --git a/backend/src/main/java/ca/bc/gov/app/service/client/ClientService.java b/backend/src/main/java/ca/bc/gov/app/service/client/ClientService.java index 09392d082d..4218aee435 100644 --- a/backend/src/main/java/ca/bc/gov/app/service/client/ClientService.java +++ b/backend/src/main/java/ca/bc/gov/app/service/client/ClientService.java @@ -25,6 +25,8 @@ import ca.bc.gov.app.repository.client.ProvinceCodeRepository; import ca.bc.gov.app.service.bcregistry.BcRegistryService; import ca.bc.gov.app.service.ches.ChesService; +import io.micrometer.observation.annotation.Observed; +import io.micrometer.tracing.annotation.SpanTag; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -44,6 +46,7 @@ @Service @RequiredArgsConstructor @Slf4j +@Observed public class ClientService { private final ClientTypeCodeRepository clientTypeCodeRepository; @@ -65,7 +68,7 @@ public class ClientService { * @return A list of {@link CodeNameDto} */ public Flux findActiveClientTypeCodes(LocalDate targetDate) { - + log.info("Loading active client type codes from the client service."); return clientTypeCodeRepository .findActiveAt(targetDate) @@ -87,6 +90,7 @@ public Flux findActiveClientTypeCodes(LocalDate targetDate) { * @return A list of {@link CodeNameDto} entries. */ public Flux listCountries(int page, int size) { + log.info("Requesting a list of countries from the client service."); return countryCodeRepository .findBy(PageRequest.of(page, size, Sort.by("order", "description"))) .map(entity -> new CodeNameDto(entity.getCountryCode(), entity.getDescription())); @@ -105,6 +109,8 @@ public Flux listCountries(int page, int size) { * @see CodeNameDto */ public Mono getCountryByCode(String countryCode) { + log.info("Loading country information for country code {} from the client service.", + countryCode); return countryCodeRepository .findByCountryCode(countryCode) .map(entity -> new CodeNameDto(entity.getCountryCode(), @@ -123,6 +129,8 @@ public Mono getCountryByCode(String countryCode) { * @see CodeNameDto */ public Mono getClientTypeByCode(String code) { + log.info("Loading client type information for client type code {} from the client service.", + code); return clientTypeCodeRepository .findByCode(code) .map(entity -> new CodeNameDto(entity.getCode(), @@ -140,6 +148,7 @@ public Mono getClientTypeByCode(String code) { * @return A list of {@link CodeNameDto} entries. */ public Flux listProvinces(String countryCode, int page, int size) { + log.info("Loading a list of provinces/states for country {} from the client service.",countryCode); return provinceCodeRepository .findByCountryCode(countryCode, PageRequest.of(page, size, Sort.by("description"))) .map(entity -> new CodeNameDto(entity.getProvinceCode(), entity.getDescription())); @@ -153,7 +162,10 @@ public Flux listProvinces(String countryCode, int page, int size) { * @param size The amount of entries per page. * @return A list of {@link CodeNameDto} entries. */ - public Flux listClientContactTypeCodes(LocalDate activeDate, int page, int size) { + + public Flux listClientContactTypeCodes(LocalDate activeDate,int page, int size) { + log.info("Loading a list of contact types from the client service."); + return contactTypeCodeRepository .findActiveAt(activeDate, PageRequest.of(page, size)) .map(entity -> new CodeNameDto( @@ -173,7 +185,7 @@ public Mono getClientDetails( String userId, String businessId ) { - log.info("Loading details for {}", clientNumber); + log.info("Loading details for client {}", clientNumber); return bcRegistryService .requestDocumentData(clientNumber) @@ -248,6 +260,7 @@ public Mono getClientDetails( * @throws InvalidAccessTokenException if the access token is invalid or expired */ public Flux findByClientNameOrIncorporation(String value) { + log.info("Searching for client by name or incorporation on bc registry {}", value); return bcRegistryService .searchByFacets(value) .map(entry -> new ClientLookUpDto( @@ -282,6 +295,8 @@ public Mono triggerEmailDuplicatedClient( String userId, String businessId ) { + log.info("Searching on Oracle legacy db for {} {}", emailRequestDto.incorporation(), + emailRequestDto.name()); return legacyService .searchLegacy( @@ -291,6 +306,10 @@ public Mono triggerEmailDuplicatedClient( businessId ) .next() + .doOnNext(legacy -> + log.info("Found legacy entry for {} {}", emailRequestDto.incorporation(), + emailRequestDto.name()) + ) .flatMap( triggerEmailDuplicatedClient(emailRequestDto.email(), emailRequestDto.userName())) .then(); @@ -456,7 +475,7 @@ private Predicate isMatchWith(BcRegistryDocumentDto document) { private Function> triggerEmailDuplicatedClient( String email, String userName) { - + log.info("Sending matched email using CHES Client number application can’t go ahead"); return legacy -> chesService.sendEmail( "matched", email, @@ -467,6 +486,7 @@ private Function> triggerEmailDuplicatedC } private Mono triggerEmail(EmailRequestDto emailRequestDto) { + log.info("Sending {} email using CHES {}", emailRequestDto.templateName(),emailRequestDto.subject()); return chesService.sendEmail( emailRequestDto.templateName(), emailRequestDto.email(), diff --git a/backend/src/main/java/ca/bc/gov/app/service/client/ClientSubmissionService.java b/backend/src/main/java/ca/bc/gov/app/service/client/ClientSubmissionService.java index e6167a70f6..d1ee784f53 100644 --- a/backend/src/main/java/ca/bc/gov/app/service/client/ClientSubmissionService.java +++ b/backend/src/main/java/ca/bc/gov/app/service/client/ClientSubmissionService.java @@ -33,6 +33,7 @@ import ca.bc.gov.app.repository.client.SubmissionMatchDetailRepository; import ca.bc.gov.app.repository.client.SubmissionRepository; import ca.bc.gov.app.service.ches.ChesService; +import io.micrometer.observation.annotation.Observed; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -58,6 +59,7 @@ @Service @RequiredArgsConstructor @Slf4j +@Observed public class ClientSubmissionService { private final SubmissionRepository submissionRepository; @@ -95,7 +97,7 @@ public Flux listSubmissions( .flatMapMany(clientTypes -> loadSubmissions(page, size, requestType, requestStatus, updatedAt) .flatMap(submission -> - loadSubmissionDetail(clientType, name, submission) + loadSubmissionDetail(clientType, name, submission.getSubmissionId()) .map(submissionDetail -> new ClientListSubmissionDto( submission.getSubmissionId(), @@ -144,17 +146,23 @@ public Mono submit( .updatedBy(userName) .build() ) + .doOnNext(submission -> log.info("Submission ready to be saved: {}", submission)) //Save submission to begin with .flatMap(submissionRepository::save) + .doOnNext(submission -> log.info("Submission saved: {}", submission)) //Save the submission detail .map(submission -> mapToSubmissionDetailEntity(submission.getSubmissionId(), clientSubmissionDto.businessInformation()) ) + .doOnNext(submissionDetail -> log.info("Submission detail ready to be saved: {}", + submissionDetail)) .flatMap(submissionDetailRepository::save) + .doOnNext(submissionDetail -> log.info("Submission detail saved: {}", submissionDetail)) //Save the locationNames and contacts and do the association .flatMap(submission -> //Save all locationNames saveAddresses(clientSubmissionDto, submission) + .doOnNext(locations -> log.info("Locations saved: {}", locations)) //For each contact, save it, // then find the associated location and save the association .flatMapMany(locations -> @@ -176,6 +184,8 @@ public Mono submit( ) //Then grab all back as a list, to make all reactive flows complete .collectList() + .doOnNext(submissionLocationContacts -> + log.info("Location contacts saved: {}", submissionLocationContacts)) //Return what we need only .thenReturn(submission.getSubmissionId()) ) @@ -255,16 +265,19 @@ public Mono getSubmissionDetail(Long id) { .all(); return detailsBusiness + .doOnNext(submissionDetailsDto -> log.info("Loaded submission details for id {}", id)) .flatMap(submissionDetailsDto -> contacts .collectList() .map(submissionDetailsDto::withContact) ) + .doOnNext(submissionDetailsDto -> log.info("Loaded submission contacts for id {}", id)) .flatMap(submissionDetailsDto -> addresses .collectList() .map(submissionDetailsDto::withAddress) ) + .doOnNext(submissionDetailsDto -> log.info("Loaded submission addresses for id {}", id)) .flatMap(submissionDetailsDto -> submissionMatchDetailRepository .findBySubmissionId(id.intValue()) @@ -273,6 +286,7 @@ public Mono getSubmissionDetail(Long id) { .withApprovedTimestamp(matched.getUpdatedAt()) .withMatchers(matched.getMatchers()) ) + .doOnNext(match -> log.info("Loaded submission match for id {}", id)) .defaultIfEmpty( submissionDetailsDto .withMatchers(Map.of()) @@ -289,6 +303,7 @@ public Mono approveOrReject( String userName, SubmissionApproveRejectDto request ) { + log.info("Request {} is being {}", id, request.approved() ? "approved" : "rejected"); return submissionRepository .findById(id.intValue()) @@ -301,6 +316,8 @@ public Mono approveOrReject( return submission; }) .flatMap(submissionRepository::save) + .doOnNext(submission -> log.info("Submission {} is now {}", id, + submission.getSubmissionStatus())) .flatMap(submission -> submissionMatchDetailRepository .findBySubmissionId(id.intValue()) @@ -310,6 +327,7 @@ public Mono approveOrReject( .withUpdatedAt(LocalDateTime.now()) .withMatchingMessage(processRejectionReason(request)) ) + .doOnNext(matched -> log.info("Match for submission {} is now being saved {}", id, matched.getStatus())) .flatMap(submissionMatchDetailRepository::save) ) .then(); @@ -322,6 +340,8 @@ private Mono saveAndAssociateContact( String userId ) { + log.info("Saving contact {} for submission {}", contact.lastName(), submissionId); + return Mono .just(mapToSubmissionContactEntity(contact)) @@ -335,19 +355,26 @@ private Mono saveAndAssociateContact( .submissionContactId(contactEntity.getSubmissionContactId()) .build() ) - .flatMap(submissionLocationContactRepository::save); + .flatMap(submissionLocationContactRepository::save) + .doOnNext(submissionLocationContactEntity -> + log.info("Contact {} saved for submission {}", contact.lastName(), submissionId) + ); } private Mono> saveAddresses( ClientSubmissionDto clientSubmissionDto, SubmissionDetailEntity submission) { + log.info("Saving location for submission {}", submission.getSubmissionId()); return submissionLocationRepository .saveAll( mapAllToSubmissionLocationEntity( submission.getSubmissionId(), clientSubmissionDto.location().addresses()) ) - .collectList(); + .collectList() + .doOnNext(submissionLocationEntities -> + log.info("Location saved for submission {}", submission.getSubmissionId()) + ); } private Mono sendEmail( @@ -356,6 +383,7 @@ private Mono sendEmail( String email, String userName ) { + log.info("Sending email to {} for submission {}", email, submissionId); return chesService.sendEmail( "registration", email, @@ -367,6 +395,7 @@ private Mono sendEmail( private Mono> getClientTypes() { + log.info("Loading client types"); return template .select( query( @@ -385,18 +414,20 @@ private Mono> getClientTypes() { .stream() .collect(Collectors.toMap(ClientTypeCodeEntity::getCode, ClientTypeCodeEntity::getDescription)) - ); + ) + .doOnNext(clientTypes -> log.info("Loaded client types from the database")); } - private Mono loadSubmissionDetail(String[] clientType, - String[] name, SubmissionEntity submission) { + private Mono loadSubmissionDetail( + String[] clientType, String[] name, Integer submissionId + ) { return template .selectOne( query( Criteria .where(ApplicationConstant.SUBMISSION_ID) - .is(submission.getSubmissionId()) + .is(submissionId) .and( QueryPredicates .orEqualTo(clientType, "clientTypeCode") @@ -410,6 +441,9 @@ private Mono loadSubmissionDetail(String[] clientType, private Flux loadSubmissions(int page, int size, String[] requestType, SubmissionStatusEnum[] requestStatus, String[] updatedAt) { + log.info("Loading submissions from the database with page {} size {} type {} status {} updated {}", + page, size, requestType, requestStatus, updatedAt); + Criteria userQuery = SubmissionPredicates .orUpdatedAt(updatedAt) .and(SubmissionPredicates.orStatus(requestStatus)) diff --git a/backend/src/main/java/ca/bc/gov/app/service/cognito/CognitoService.java b/backend/src/main/java/ca/bc/gov/app/service/cognito/CognitoService.java index 7fd7692b7f..561c77f2b3 100644 --- a/backend/src/main/java/ca/bc/gov/app/service/cognito/CognitoService.java +++ b/backend/src/main/java/ca/bc/gov/app/service/cognito/CognitoService.java @@ -6,6 +6,7 @@ import ca.bc.gov.app.dto.cognito.RefreshResponseDto; import ca.bc.gov.app.dto.cognito.RefreshResponseResultDto; import ca.bc.gov.app.util.PkceUtil; +import io.micrometer.observation.annotation.Observed; import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.Optional; @@ -25,6 +26,7 @@ */ @Service @Slf4j +@Observed public class CognitoService { private final ForestClientConfiguration configuration; diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 15e6f25251..94e8e5bd3f 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: application: - name: FSA Forest Client + name: nr-forest-client-${APP_COMPONENT:backend} main: lazy-initialization: false banner-mode: "off" @@ -30,6 +30,15 @@ spring: charset: UTF-8 enabled: true force: true + sleuth: + propagation-keys: + - X-TRACE-ID + baggage-keys: + - X-TRACE-ID + log: + slf4j: + whitelisted-mdc-keys: + - X-TRACE-ID management: enable-native-support: true @@ -42,14 +51,28 @@ management: prometheus: metrics base-path: / exposure: - include: health,info,prometheus,httptrace + include: health,info,metrics,otlp,tracing httpexchanges: recording: - include: principal,request-headers,response-headers,cookie-headers,time-taken,authorization-header,remote-address,session-id + include: principal,request-headers,response-headers,cookie-headers,time-taken,authorization-header,remote-address,session-id,traceId,spanId + tracing: + enabled: false + baggage: + remote-fields: + - X-TRACE-ID + correlation: + fields: + - X-TRACE-ID + sampling: + probability: 0.75 + zipkin: + metrics: + export: + enabled: true info: app: - name: nr-forest-client-${APP_COMPONENT:backend} + name: ${spring.application.name} zone: ${APP_ZONE:local} ca: @@ -129,3 +152,27 @@ ca: - PUT - DELETE age: 5m + +logging: + pattern: + correlation: "[${spring.application.name:},%X{${X-TRACE-ID:traceId}:-},%X{spanId:-}] " + +# Profile Specific Properties +--- +spring: + config: + activate: + on-profile: container + sleuth: + propagation-keys: + - X-TRACE-ID + baggage-keys: + - X-TRACE-ID + log: + slf4j: + whitelisted-mdc-keys: + - X-TRACE-ID + +logging: + pattern: + console: "{\"time\": \"%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}\", \"level\": \"${LOG_LEVEL:%5p}\", \"app\":\"${spring.application.name:-}\", \"traceId\":\"%X{${X-TRACE-ID:traceId}:-}\", \"spanId\":\"%X{spanId:-}\", \"pid\": \"${PID: }\", \"thread\": \"%t\", \"source\": \"%logger{63}:%L\", \"message\": \"%m${LOG_EXCEPTION_CONVERSION_WORD:%wEx}\"}%n" diff --git a/frontend/Caddyfile b/frontend/Caddyfile index d6cec0167d..b5e25a7508 100644 --- a/frontend/Caddyfile +++ b/frontend/Caddyfile @@ -1,6 +1,9 @@ { auto_https off - admin off + admin 0.0.0.0:80 + servers { + metrics + } } :3000 diff --git a/frontend/openshift.deploy.yml b/frontend/openshift.deploy.yml index 387ec698bc..32e6ecef29 100644 --- a/frontend/openshift.deploy.yml +++ b/frontend/openshift.deploy.yml @@ -107,6 +107,10 @@ objects: type: Rolling template: metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "3000" + prometheus.io/path: "/metrics" labels: app: ${NAME}-${ZONE} deploymentconfig: ${NAME}-${ZONE}-${COMPONENT} diff --git a/legacy/Dockerfile b/legacy/Dockerfile index f166544d74..910e79dd78 100644 --- a/legacy/Dockerfile +++ b/legacy/Dockerfile @@ -35,4 +35,4 @@ EXPOSE ${PORT} HEALTHCHECK CMD curl -f http://localhost:${PORT}/actuator/health | grep '"status":"UP"' # Startup -ENTRYPOINT ["/app/nr-forest-client-legacy"] \ No newline at end of file +ENTRYPOINT ["/app/nr-forest-client-legacy", "--spring.profiles.active=container"] \ No newline at end of file diff --git a/legacy/pom.xml b/legacy/pom.xml index 60dc81999a..0b652e20c1 100644 --- a/legacy/pom.xml +++ b/legacy/pom.xml @@ -83,6 +83,19 @@ io.micrometer micrometer-registry-prometheus + + io.micrometer + micrometer-tracing-bridge-otel + + + io.opentelemetry + opentelemetry-exporter-zipkin + + + org.springframework.experimental + r2dbc-micrometer-spring-boot + 1.0.2 + com.oracle.database.r2dbc @@ -189,6 +202,20 @@ reactor-netty-http 1.1.13 + + io.micrometer + micrometer-bom + ${micrometer.version} + pom + import + + + io.micrometer + micrometer-tracing-bom + ${micrometer-tracing.version} + pom + import + @@ -212,6 +239,18 @@ org.graalvm.buildtools native-maven-plugin + + false + ca.bc.gov.app.LegacyApplication + + -H:+ReportExceptionStackTraces + + --trace-class-initialization=org.apache.commons.logging.LogFactoryService,io.micrometer.common.util.internal.logging.LocationAwareSlf4JLogger,ch.qos.logback.classic.Logger + --trace-class-initialization=org.apache.commons.logging.LogFactory,io.micrometer.common.util.internal.logging.LocationAwareSlf4JLogger,ch.qos.logback.classic.Logger + + --initialize-at-build-time=org.apache.commons.logging.LogFactoryService,io.micrometer.common.util.internal.logging.LocationAwareSlf4JLogger,ch.qos.logback.classic.Logger + + diff --git a/legacy/src/main/java/ca/bc/gov/app/LegacyApplication.java b/legacy/src/main/java/ca/bc/gov/app/LegacyApplication.java index cce874711b..79f633bee7 100644 --- a/legacy/src/main/java/ca/bc/gov/app/LegacyApplication.java +++ b/legacy/src/main/java/ca/bc/gov/app/LegacyApplication.java @@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; +import reactor.core.publisher.Hooks; @SpringBootApplication @EnableScheduling @@ -12,6 +13,7 @@ public class LegacyApplication { public static void main(String[] args) { SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); + Hooks.enableAutomaticContextPropagation(); SpringApplication.run(LegacyApplication.class, args); } diff --git a/legacy/src/main/java/ca/bc/gov/app/configuration/TracingConfiguration.java b/legacy/src/main/java/ca/bc/gov/app/configuration/TracingConfiguration.java new file mode 100644 index 0000000000..11ace51b5b --- /dev/null +++ b/legacy/src/main/java/ca/bc/gov/app/configuration/TracingConfiguration.java @@ -0,0 +1,21 @@ +package ca.bc.gov.app.configuration; + +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservedAspect; +import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@Slf4j +public class TracingConfiguration { + + @Bean + ObservedAspect observedAspect( + ObservationRegistry observationRegistry + ) { + ObservationThreadLocalAccessor.getInstance().setObservationRegistry(observationRegistry); + return new ObservedAspect(observationRegistry); + } +} diff --git a/legacy/src/main/java/ca/bc/gov/app/controller/ClientContactController.java b/legacy/src/main/java/ca/bc/gov/app/controller/ClientContactController.java index 66461fe6a5..9bc38b147f 100644 --- a/legacy/src/main/java/ca/bc/gov/app/controller/ClientContactController.java +++ b/legacy/src/main/java/ca/bc/gov/app/controller/ClientContactController.java @@ -2,6 +2,7 @@ import ca.bc.gov.app.dto.ForestClientContactDto; import ca.bc.gov.app.service.ClientContactService; +import io.micrometer.observation.annotation.Observed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -20,6 +21,7 @@ @Slf4j @RequestMapping(value = "/api/contacts", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor +@Observed public class ClientContactController { private final ClientContactService service; @@ -27,6 +29,7 @@ public class ClientContactController { @PostMapping @ResponseStatus(HttpStatus.CREATED) public Mono saveLocation(@RequestBody ForestClientContactDto dto) { + log.info("Receiving request to save contact for {}: {}", dto.clientNumber(), dto.contactName()); return service.saveAndGetIndex(dto); } @@ -37,6 +40,8 @@ public Flux findIndividuals( @RequestParam String email, @RequestParam String phone ) { + log.info("Receiving request to search for contact: {} {} {} {}", firstName, lastName, email, + phone); return service.search(firstName, lastName, email, phone); } diff --git a/legacy/src/main/java/ca/bc/gov/app/controller/ClientController.java b/legacy/src/main/java/ca/bc/gov/app/controller/ClientController.java index 5f5fccd74b..1c132a1ddd 100644 --- a/legacy/src/main/java/ca/bc/gov/app/controller/ClientController.java +++ b/legacy/src/main/java/ca/bc/gov/app/controller/ClientController.java @@ -2,6 +2,7 @@ import ca.bc.gov.app.dto.ForestClientDto; import ca.bc.gov.app.service.ClientService; +import io.micrometer.observation.annotation.Observed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -17,6 +18,7 @@ @Slf4j @RequestMapping(value = "/api/clients", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor +@Observed public class ClientController { @@ -24,7 +26,8 @@ public class ClientController { @PostMapping @ResponseStatus(HttpStatus.CREATED) - public Mono saveLocation(@RequestBody ForestClientDto dto){ + public Mono saveClient(@RequestBody ForestClientDto dto){ + log.info("Receiving request to save client {}: {}", dto.clientNumber(), dto.clientName()); return service.saveAndGetIndex(dto); } diff --git a/legacy/src/main/java/ca/bc/gov/app/controller/ClientDoingBusinessAsController.java b/legacy/src/main/java/ca/bc/gov/app/controller/ClientDoingBusinessAsController.java index 048df5eac1..14852ea51c 100644 --- a/legacy/src/main/java/ca/bc/gov/app/controller/ClientDoingBusinessAsController.java +++ b/legacy/src/main/java/ca/bc/gov/app/controller/ClientDoingBusinessAsController.java @@ -3,6 +3,7 @@ import ca.bc.gov.app.dto.ClientDoingBusinessAsDto; import ca.bc.gov.app.exception.NoValueFoundException; import ca.bc.gov.app.service.ClientDoingBusinessAsService; +import io.micrometer.observation.annotation.Observed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -21,6 +22,7 @@ @Slf4j @RequestMapping(value = "/api/dba", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor +@Observed public class ClientDoingBusinessAsController { private final ClientDoingBusinessAsService service; @@ -28,11 +30,14 @@ public class ClientDoingBusinessAsController { @PostMapping @ResponseStatus(HttpStatus.CREATED) public Mono saveLocation(@RequestBody ClientDoingBusinessAsDto dto) { + log.info("Receiving request to save client doing business as for {}: {}", dto.clientNumber(), + dto.doingBusinessAsName()); return service.saveAndGetIndex(dto); } @GetMapping("/search") - public Flux search(@RequestParam String dbaName){ + public Flux search(@RequestParam String dbaName) { + log.info("Receiving request to search client doing business as {}", dbaName); return service.search(dbaName).switchIfEmpty(Flux.error(new NoValueFoundException(dbaName))); } diff --git a/legacy/src/main/java/ca/bc/gov/app/controller/ClientLocationController.java b/legacy/src/main/java/ca/bc/gov/app/controller/ClientLocationController.java index 683f2abfdd..58bc41f15a 100644 --- a/legacy/src/main/java/ca/bc/gov/app/controller/ClientLocationController.java +++ b/legacy/src/main/java/ca/bc/gov/app/controller/ClientLocationController.java @@ -2,6 +2,7 @@ import ca.bc.gov.app.dto.ForestClientLocationDto; import ca.bc.gov.app.service.ClientLocationService; +import io.micrometer.observation.annotation.Observed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -17,6 +18,7 @@ @Slf4j @RequestMapping(value = "/api/locations", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor +@Observed public class ClientLocationController { private final ClientLocationService service; @@ -24,6 +26,7 @@ public class ClientLocationController { @PostMapping @ResponseStatus(HttpStatus.CREATED) public Mono saveLocation(@RequestBody ForestClientLocationDto dto){ + log.info("Receiving request to save location for {}: {}",dto.clientNumber(), dto.clientLocnName()); return service.saveAndGetIndex(dto); } diff --git a/legacy/src/main/java/ca/bc/gov/app/controller/ClientSearchController.java b/legacy/src/main/java/ca/bc/gov/app/controller/ClientSearchController.java index 858841c4dc..644af02e1c 100644 --- a/legacy/src/main/java/ca/bc/gov/app/controller/ClientSearchController.java +++ b/legacy/src/main/java/ca/bc/gov/app/controller/ClientSearchController.java @@ -2,6 +2,7 @@ import ca.bc.gov.app.dto.ForestClientDto; import ca.bc.gov.app.service.ClientSearchService; +import io.micrometer.observation.annotation.Observed; import java.time.LocalDate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -16,6 +17,7 @@ @Slf4j @RequestMapping(value = "/api/search", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor +@Observed public class ClientSearchController { private final ClientSearchService service; @@ -25,6 +27,8 @@ public Flux findByIncorporationOrName( @RequestParam(required = false) String incorporationNumber, @RequestParam(required = false) String companyName ) { + log.info("Receiving request to search by incorporation number {} or company name {}", + incorporationNumber, companyName); return service .findByIncorporationOrName(incorporationNumber, companyName); } @@ -35,6 +39,7 @@ public Flux findIndividuals( @RequestParam String lastName, @RequestParam LocalDate dob ) { + log.info("Receiving request to search by individual {} {} {}", firstName, lastName, dob); return service.findByIndividual(firstName, lastName, dob); } @@ -42,6 +47,7 @@ public Flux findIndividuals( public Flux matchBy( @RequestParam String companyName ) { + log.info("Receiving request to match by company name {}", companyName); return service.matchBy(companyName); } diff --git a/legacy/src/main/java/ca/bc/gov/app/service/ClientContactService.java b/legacy/src/main/java/ca/bc/gov/app/service/ClientContactService.java index 2ede5164e1..c32ab6f724 100644 --- a/legacy/src/main/java/ca/bc/gov/app/service/ClientContactService.java +++ b/legacy/src/main/java/ca/bc/gov/app/service/ClientContactService.java @@ -5,6 +5,7 @@ import ca.bc.gov.app.entity.ForestClientContactEntity; import ca.bc.gov.app.mappers.AbstractForestClientMapper; import ca.bc.gov.app.repository.ForestClientContactRepository; +import io.micrometer.observation.annotation.Observed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.r2dbc.core.R2dbcEntityOperations; @@ -17,6 +18,7 @@ @RequiredArgsConstructor @Service @Slf4j +@Observed public class ClientContactService { private final R2dbcEntityOperations entityTemplate; @@ -35,6 +37,21 @@ public Mono saveAndGetIndex(ForestClientContactDto dto) { ) .map(forestClientLocation -> false) // means you can't create it .defaultIfEmpty(true) // means you can create it + .doOnNext(canCreate -> + log.info( + "Can create forest client contact {} {}? {}", + locationDto.clientNumber(), + locationDto.contactName(), + canCreate + ) + ) + ) + .doOnNext(forestClientContact -> + log.info( + "Creating forest client contact {} {}", + forestClientContact.clientNumber(), + forestClientContact.contactName() + ) ) .map(mapper::toEntity) .flatMap(entity -> getNextContactId().map(entity::withClientContactId)) diff --git a/legacy/src/main/java/ca/bc/gov/app/service/ClientDoingBusinessAsService.java b/legacy/src/main/java/ca/bc/gov/app/service/ClientDoingBusinessAsService.java index 7358570798..8d9822e534 100644 --- a/legacy/src/main/java/ca/bc/gov/app/service/ClientDoingBusinessAsService.java +++ b/legacy/src/main/java/ca/bc/gov/app/service/ClientDoingBusinessAsService.java @@ -5,6 +5,7 @@ import ca.bc.gov.app.entity.ClientDoingBusinessAsEntity; import ca.bc.gov.app.mappers.AbstractForestClientMapper; import ca.bc.gov.app.repository.ClientDoingBusinessAsRepository; +import io.micrometer.observation.annotation.Observed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.r2dbc.core.R2dbcEntityOperations; @@ -17,6 +18,7 @@ @RequiredArgsConstructor @Service @Slf4j +@Observed public class ClientDoingBusinessAsService { private final R2dbcEntityOperations entityTemplate; @@ -34,6 +36,14 @@ public Mono saveAndGetIndex(ClientDoingBusinessAsDto dto) { ) .map(forestClientLocation -> false) // means you can't create it .defaultIfEmpty(true) // means you can create it + .doOnNext(canCreate -> + log.info( + "Can create client doing business as {} {}? {}", + dto.clientNumber(), + dto.doingBusinessAsName(), + canCreate + ) + ) ) .map(mapper::toEntity) .flatMap(entity -> getNextDoingBusinessAs().map(entity::withId)) @@ -88,7 +98,13 @@ private Mono getNextDoingBusinessAs() { } public Flux search(String dbaName) { - return repository.matchBy(dbaName) + return repository + .matchBy(dbaName) + .doOnNext(dba -> log.info( + "Found forest client doing business as {} {}", + dba.getClientNumber(), + dba.getDoingBusinessAsName() + )) .map(mapper::toDto); } } diff --git a/legacy/src/main/java/ca/bc/gov/app/service/ClientLocationService.java b/legacy/src/main/java/ca/bc/gov/app/service/ClientLocationService.java index 3265d389f5..d5f457b606 100644 --- a/legacy/src/main/java/ca/bc/gov/app/service/ClientLocationService.java +++ b/legacy/src/main/java/ca/bc/gov/app/service/ClientLocationService.java @@ -4,6 +4,7 @@ import ca.bc.gov.app.dto.ForestClientLocationDto; import ca.bc.gov.app.entity.ForestClientLocationEntity; import ca.bc.gov.app.mappers.AbstractForestClientMapper; +import io.micrometer.observation.annotation.Observed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.r2dbc.core.R2dbcEntityOperations; @@ -15,6 +16,7 @@ @RequiredArgsConstructor @Service @Slf4j +@Observed public class ClientLocationService { private final R2dbcEntityOperations entityTemplate; @@ -28,12 +30,27 @@ public Mono saveAndGetIndex(ForestClientLocationDto dto) { locateClientLocation(locationDto.clientNumber(), locationDto.clientLocnCode()) .map(forestClientLocation -> false) // means you can't create it .defaultIfEmpty(true) // means you can create it + .doOnNext(canCreate -> + log.info( + "Can create client location {} {}? {}", + locationDto.clientNumber(), + locationDto.clientLocnName(), + canCreate + ) + ) ) .map(mapper::toEntity) .flatMap(entity -> entityTemplate .insert(ForestClientLocationEntity.class) .using(entity) ) + .doOnNext(forestClientContact -> + log.info( + "Saved forest client location {} {}", + forestClientContact.getClientNumber(), + forestClientContact.getClientLocnName() + ) + ) .map(ForestClientLocationEntity::getClientNumber); } diff --git a/legacy/src/main/java/ca/bc/gov/app/service/ClientSearchService.java b/legacy/src/main/java/ca/bc/gov/app/service/ClientSearchService.java index 1fc13c6383..493b6b8bff 100644 --- a/legacy/src/main/java/ca/bc/gov/app/service/ClientSearchService.java +++ b/legacy/src/main/java/ca/bc/gov/app/service/ClientSearchService.java @@ -7,6 +7,7 @@ import ca.bc.gov.app.mappers.AbstractForestClientMapper; import ca.bc.gov.app.repository.ClientDoingBusinessAsRepository; import ca.bc.gov.app.repository.ForestClientRepository; +import io.micrometer.observation.annotation.Observed; import java.time.LocalDate; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -19,6 +20,7 @@ @Service @RequiredArgsConstructor @Slf4j +@Observed public class ClientSearchService { private final ForestClientRepository forestClientRepository; @@ -31,6 +33,7 @@ public Flux findByIncorporationOrName( ) { if (StringUtils.isAllBlank(incorporationNumber, companyName)) { + log.error("Missing required parameter to search for incorporation or company name"); throw new MissingRequiredParameterException("incorporationNumber or companyName"); } @@ -62,6 +65,7 @@ public Flux findByIndividual( ) { if (StringUtils.isAnyBlank(firstName, lastName) || dob == null) { + log.error("Missing required parameter to search for individual"); throw new MissingRequiredParameterException("firstName, lastName, or dob"); } diff --git a/legacy/src/main/java/ca/bc/gov/app/service/ClientService.java b/legacy/src/main/java/ca/bc/gov/app/service/ClientService.java index d8faccd388..4299cafe59 100644 --- a/legacy/src/main/java/ca/bc/gov/app/service/ClientService.java +++ b/legacy/src/main/java/ca/bc/gov/app/service/ClientService.java @@ -4,6 +4,7 @@ import ca.bc.gov.app.entity.ForestClientEntity; import ca.bc.gov.app.mappers.AbstractForestClientMapper; import ca.bc.gov.app.repository.ForestClientRepository; +import io.micrometer.observation.annotation.Observed; import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -15,6 +16,7 @@ @Service @RequiredArgsConstructor @Slf4j +@Observed public class ClientService { private final R2dbcEntityOperations entityTemplate; @@ -78,7 +80,7 @@ private Mono locateClient( ) .map(client -> false) // means you can't create it .defaultIfEmpty(true) - .doOnNext(tag -> log.info("No individual client found {}", tag)) + .doOnNext(tag -> log.info("No individual client found? {}", tag)) .last(); } @@ -94,7 +96,7 @@ private Mono locateClient( ) .map(client -> false) // means you can't create it .defaultIfEmpty(true) - .doOnNext(tag -> log.info("No client with type {} found {}", entity + .doOnNext(tag -> log.info("No client with type {} found? {}", entity .getClientTypeCode(), tag)) .last(); } diff --git a/legacy/src/main/resources/application.yml b/legacy/src/main/resources/application.yml index d02389f0e8..fd9cf9ddd0 100644 --- a/legacy/src/main/resources/application.yml +++ b/legacy/src/main/resources/application.yml @@ -3,7 +3,7 @@ server: spring: application: - name: FSA Legacy Forest Client + name: nr-forest-client-${APP_COMPONENT:legacy} main: lazy-initialization: false banner-mode: "off" @@ -21,6 +21,20 @@ spring: maxIdleTime: 45000 maxCreateConnectionTime: 90000 poolName: FsaLegacyClientConPool + http: + encoding: + charset: UTF-8 + enabled: true + force: true + sleuth: + propagation-keys: + - X-TRACE-ID + baggage-keys: + - X-TRACE-ID + log: + slf4j: + whitelisted-mdc-keys: + - X-TRACE-ID management: enable-native-support: true @@ -33,15 +47,28 @@ management: prometheus: metrics base-path: / exposure: - include: health,info,prometheus,httptrace + include: health,info,metrics,otlp,tracing httpexchanges: recording: include: principal,request-headers,response-headers,cookie-headers,time-taken,authorization-header,remote-address,session-id - + tracing: + enabled: false + baggage: + remote-fields: + - X-TRACE-ID + correlation: + fields: + - X-TRACE-ID + sampling: + probability: 0.75 + zipkin: + metrics: + export: + enabled: true info: app: - name: nr-forest-client-${APP_COMPONENT:legacy} + name: ${spring.application.name} zone: ${APP_ZONE:local} ca: @@ -58,4 +85,29 @@ ca: username: ${ORACLEDB_USER:user} password: ${ORACLEDB_PASSWORD:passwd} keystore: ${ORACLEDB_KEYSTORE:jssecacerts.jks} - secret: ${ORACLEDB_SECRET:changeit} \ No newline at end of file + secret: ${ORACLEDB_SECRET:changeit} + + +logging: + pattern: + correlation: "[${spring.application.name:},%X{${X-TRACE-ID:traceId}:-},%X{spanId:-}] " + +# Profile Specific Properties +--- +spring: + config: + activate: + on-profile: container + sleuth: + propagation-keys: + - X-TRACE-ID + baggage-keys: + - X-TRACE-ID + log: + slf4j: + whitelisted-mdc-keys: + - X-TRACE-ID + +logging: + pattern: + console: "{\"time\": \"%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}\", \"level\": \"${LOG_LEVEL:%5p}\", \"app\":\"${spring.application.name:-}\", \"traceId\":\"%X{${X-TRACE-ID:traceId}:-}\", \"spanId\":\"%X{spanId:-}\", \"pid\": \"${PID: }\", \"thread\": \"%t\", \"source\": \"%logger{63}:%L\", \"message\": \"%m${LOG_EXCEPTION_CONVERSION_WORD:%wEx}\"}%n"