Skip to content

Commit 500f067

Browse files
committed
Improves application logging and monitoring
Adds structured logging using Logstash to enhance application monitoring and debugging. Implements detailed logging for exceptions and scheduler tasks, providing contextual information such as transaction IDs, status codes, and relevant data. This allows for better traceability and faster issue resolution. The custom exception handler now logs warnings and errors with relevant context, including exception type, HTTP status, and error messages. Scheduler tasks now include a transaction ID for easier tracking of background processes.
1 parent f568b21 commit 500f067

File tree

10 files changed

+119
-47
lines changed

10 files changed

+119
-47
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@
8484
<artifactId>jackson-datatype-jsr310</artifactId>
8585
</dependency>
8686

87+
<dependency>
88+
<groupId>net.logstash.logback</groupId>
89+
<artifactId>logstash-logback-encoder</artifactId>
90+
<version>9.0</version>
91+
</dependency>
8792
</dependencies>
8893

8994
<build>

src/main/java/com/dmware/api_onibusbh/infra/CustomExceptionHandler.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.dmware.api_onibusbh.infra;
22

33
import com.dmware.api_onibusbh.exceptions.*;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
46
import org.springframework.http.HttpStatus;
57
import org.springframework.http.ResponseEntity;
68
import org.springframework.web.bind.annotation.ControllerAdvice;
@@ -11,54 +13,66 @@
1113

1214
import java.io.IOException;
1315

16+
import static net.logstash.logback.argument.StructuredArguments.kv;
17+
1418
@ControllerAdvice
1519
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
1620

21+
private static final Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
22+
1723
@ExceptionHandler(LinhasNotFoundException.class)
1824
public ResponseEntity<ErrorResponse> linhasNotFoundException(LinhasNotFoundException ex) {
25+
logger.warn("Recurso não encontrado", kv("exception", ex.getClass().getSimpleName()), kv("status", HttpStatus.NOT_FOUND));
1926
return ErrorResponse.of("Não foi encontrada nenhuma linha, por favor tente novamente", HttpStatus.NOT_FOUND);
2027
}
2128

2229
@ExceptionHandler(CoordenadasNotFoundException.class)
2330
public ResponseEntity<ErrorResponse> coordenadasNotFoundException(CoordenadasNotFoundException ex) {
31+
logger.warn("Recurso não encontrado", kv("exception", ex.getClass().getSimpleName()), kv("status", HttpStatus.NOT_FOUND));
2432
return ErrorResponse.of("Não foi encontrada nenhuma coordenada, por favor tente novamente",
2533
HttpStatus.NOT_FOUND);
2634
}
2735

2836
@ExceptionHandler(DicionarioNotFoundException.class)
2937
public ResponseEntity<ErrorResponse> dicionarioNotFoundException(DicionarioNotFoundException ex) {
38+
logger.warn("Recurso não encontrado", kv("exception", ex.getClass().getSimpleName()), kv("status", HttpStatus.NOT_FOUND));
3039
return ErrorResponse.of("Não foi encontrado o dicionário para os dados, por favor tente novamente",
3140
HttpStatus.NOT_FOUND);
3241
}
3342

3443
@ExceptionHandler(LinhaNotFoundException.class)
3544
public ResponseEntity<ErrorResponse> linhaNotFoundException(LinhaNotFoundException ex) {
45+
logger.warn("Recurso não encontrado", kv("exception", ex.getClass().getSimpleName()), kv("status", HttpStatus.NOT_FOUND));
3646
return ErrorResponse.of("Não foi encontrada nenhuma linha, por favor tente novamente", HttpStatus.NOT_FOUND);
3747
}
3848

3949
@ExceptionHandler(ValidJsonException.class)
4050
public ResponseEntity<ErrorResponse> validJsonException(ValidJsonException ex) {
51+
logger.warn("Erro de validação JSON", kv("exception", ex.getClass().getSimpleName()), kv("status", HttpStatus.BAD_REQUEST), kv("mensagem_erro", ex.getMessage()));
4152
return ErrorResponse.of(ex.getMessage(), HttpStatus.BAD_REQUEST);
4253
}
4354

4455
@ExceptionHandler(RuntimeException.class)
4556
public ResponseEntity<ErrorResponse> runtimeException(RuntimeException ex) {
46-
logger.error(ex.getMessage(), ex);
57+
logger.error("Erro interno não tratado", kv("exception", ex.getClass().getSimpleName()), kv("status", HttpStatus.INTERNAL_SERVER_ERROR), ex);
4758
return ErrorResponse.of("Ocorreu um erro interno, por favor tente novamente", HttpStatus.INTERNAL_SERVER_ERROR);
4859
}
4960

5061
@ExceptionHandler(IOException.class)
5162
public ResponseEntity<ErrorResponse> ioException(IOException ex) {
63+
logger.error("Erro interno não tratado", kv("exception", ex.getClass().getSimpleName()), kv("status", HttpStatus.INTERNAL_SERVER_ERROR), ex);
5264
return ErrorResponse.of("Ocorreu um erro interno, por favor tente novamente", HttpStatus.INTERNAL_SERVER_ERROR);
5365
}
5466

5567
@ExceptionHandler(NoResourceFoundException.class)
5668
public ResponseEntity<ErrorResponse> noResourceFoundException(NoResourceFoundException ex) {
69+
logger.warn("Recurso não encontrado", kv("exception", ex.getClass().getSimpleName()), kv("status", HttpStatus.NOT_FOUND));
5770
return ErrorResponse.of("Verifique a rota digitada ou os dados enviados", HttpStatus.NOT_FOUND);
5871
}
5972

6073
@ExceptionHandler(NoHandlerFoundException.class)
6174
public ResponseEntity<ErrorResponse> noHandlerFoundException(NoHandlerFoundException ex) {
75+
logger.warn("Recurso não encontrado", kv("exception", ex.getClass().getSimpleName()), kv("status", HttpStatus.NOT_FOUND));
6276
return ErrorResponse.of("Verifique a rota digitada ou os dados enviados", HttpStatus.NOT_FOUND);
6377
}
6478
}

src/main/java/com/dmware/api_onibusbh/scheduler/CoordenadasScheduler.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import java.io.IOException;
44
import java.util.List;
5+
import java.util.UUID;
56
import java.util.concurrent.TimeUnit;
67

78
import com.dmware.api_onibusbh.dto.CoordenadaDTO;
89
import com.dmware.api_onibusbh.services.APIService;
10+
import org.slf4j.MDC;
911
import org.springframework.scheduling.annotation.Async;
1012
import org.springframework.scheduling.annotation.Scheduled;
1113
import org.springframework.stereotype.Component;
@@ -25,8 +27,13 @@ public CoordenadasScheduler(APIService apiService, OnibusService onibusService)
2527

2628
@Scheduled(fixedDelay = 20, timeUnit = TimeUnit.SECONDS)
2729
public void fetchCoordenadasOnibus() {
28-
List<CoordenadaDTO> coordenadas = apiService.getOnibusCoordenadaBH();
29-
onibusService.salvaCoordenadas(coordenadas);
30+
try {
31+
MDC.put("transaction_id", UUID.randomUUID().toString());
32+
List<CoordenadaDTO> coordenadas = apiService.getOnibusCoordenadaBH();
33+
onibusService.salvaCoordenadas(coordenadas);
34+
} finally {
35+
MDC.clear();
36+
}
3037
}
3138

3239
}

src/main/java/com/dmware/api_onibusbh/scheduler/DicionarioScheduler.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.dmware.api_onibusbh.scheduler;
22

3+
import java.util.UUID;
34
import java.util.concurrent.TimeUnit;
45

6+
import org.slf4j.MDC;
57
import org.springframework.beans.factory.annotation.Autowired;
68
import org.springframework.scheduling.annotation.Async;
79
import org.springframework.scheduling.annotation.Scheduled;
@@ -21,6 +23,11 @@ public DicionarioScheduler(DicionarioService dicionarioService) {
2123
@Async
2224
@Scheduled(fixedDelay = 12, timeUnit = TimeUnit.HOURS)
2325
public void salvaDicionarioBanco() {
24-
dicionarioService.salvarDicionarioBanco();
26+
try {
27+
MDC.put("transaction_id", UUID.randomUUID().toString());
28+
dicionarioService.salvarDicionarioBanco();
29+
} finally {
30+
MDC.clear();
31+
}
2532
}
2633
}

src/main/java/com/dmware/api_onibusbh/scheduler/LinhaScheduler.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.dmware.api_onibusbh.scheduler;
22

3+
import java.util.UUID;
34
import java.util.concurrent.TimeUnit;
45

6+
import org.slf4j.MDC;
57
import org.springframework.beans.factory.annotation.Autowired;
68
import org.springframework.scheduling.annotation.Async;
79
import org.springframework.scheduling.annotation.Scheduled;
@@ -21,7 +23,12 @@ public LinhaScheduler(LinhasService linhasService) {
2123
@Async
2224
@Scheduled(fixedDelay = 12, timeUnit = TimeUnit.HOURS)
2325
public void fetchCoordenadasOnibus() {
24-
linhasService.salvaLinhasNormais();
26+
try {
27+
MDC.put("transaction_id", UUID.randomUUID().toString());
28+
linhasService.salvaLinhasNormais();
29+
} finally {
30+
MDC.clear();
31+
}
2532
}
2633

2734
}

src/main/java/com/dmware/api_onibusbh/services/APIService.java

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package com.dmware.api_onibusbh.services;
22

3-
import java.io.IOException;
43
import java.util.ArrayList;
54
import java.util.List;
65

76
import com.dmware.api_onibusbh.dto.CoordenadaDTO;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
89
import org.springframework.stereotype.Service;
10+
import org.springframework.web.reactive.function.client.WebClient;
911
import org.springframework.web.reactive.function.client.WebClientResponseException;
1012

11-
import com.dmware.api_onibusbh.config.WebClientConfig;
1213
import com.dmware.api_onibusbh.dto.DicionarioDTO;
1314
import com.dmware.api_onibusbh.dto.LinhaDTO;
1415
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -21,18 +22,19 @@
2122
@Service
2223
public class APIService {
2324

24-
private final WebClientConfig webClientConfig;
25+
private static final Logger logger = LoggerFactory.getLogger(APIService.class);
26+
27+
private final WebClient webClient;
2528
private final ObjectMapper objectMapper;
2629

27-
public APIService(WebClientConfig webClientConfig, ObjectMapper objectMapper) {
28-
this.webClientConfig = webClientConfig;
30+
public APIService(WebClient webClient, ObjectMapper objectMapper) {
31+
this.webClient = webClient;
2932
this.objectMapper = objectMapper;
3033
}
3134

3235
public List<DicionarioDTO> getDicionarioAPIBH() {
33-
List<DicionarioDTO> dicionarios;
3436
try {
35-
String json = webClientConfig.webClient().get()
37+
String json = webClient.get()
3638
.uri("https://ckan.pbh.gov.br/api/3/action/datastore_search?resource_id=825337e5-8cd5-43d9-ac52-837d80346721&limit=20")
3739
.retrieve()
3840
.bodyToMono(String.class)
@@ -41,44 +43,33 @@ public List<DicionarioDTO> getDicionarioAPIBH() {
4143
JsonNode rootNode = objectMapper.readTree(json);
4244
JsonNode recordsNode = rootNode.path("result").path("records");
4345

44-
// Deserializa o JsonNode em uma lista de objetos DicionarioDTO
45-
dicionarios = objectMapper.readValue(recordsNode.toString(),
46-
new TypeReference<List<DicionarioDTO>>() {
47-
});
46+
return objectMapper.convertValue(recordsNode, new TypeReference<List<DicionarioDTO>>() {});
4847

4948
} catch (JsonProcessingException | WebClientResponseException e) {
5049
throw new RuntimeException(e);
5150
}
52-
53-
return dicionarios;
5451
}
5552

5653
public List<LinhaDTO> getLinhasAPIBH() {
5754
try {
58-
// Consome API PBH para buscar linhas
59-
String json = webClientConfig.webClient().get()
55+
String json = webClient.get()
6056
.uri("https://ckan.pbh.gov.br/api/3/action/datastore_search?resource_id=150bddd0-9a2c-4731-ade9-54aa56717fb6&limit=3000")
6157
.retrieve()
6258
.bodyToMono(String.class)
6359
.block();
6460

65-
// Lê o json e navega até o campo "result" -> "records"
6661
JsonNode rootNode = objectMapper.readTree(json);
6762
JsonNode recordsNode = rootNode.path("result").path("records");
6863

69-
// Deserializa o JsonNode em uma lista de objetos LinhaDTO
70-
71-
return objectMapper.readValue(recordsNode.toString(),
72-
new TypeReference<List<LinhaDTO>>() {
73-
});
64+
return objectMapper.convertValue(recordsNode, new TypeReference<List<LinhaDTO>>() {});
7465

7566
} catch (JsonProcessingException | WebClientResponseException e) {
7667
throw new RuntimeException(e);
7768
}
7869
}
7970

8071
public List<CoordenadaDTO> getOnibusCoordenadaBH() {
81-
System.out.printf("Acessando diretamente.%n");
72+
logger.info("Acessando diretamente.");
8273
List<String> responses = fetchCoordenadasDirectly();
8374

8475
if (responses == null || responses.size() < 2) {
@@ -89,17 +80,25 @@ public List<CoordenadaDTO> getOnibusCoordenadaBH() {
8980
}
9081

9182
private List<String> fetchCoordenadasDirectly() {
92-
Mono<String> monoParamD = webClientConfig.webClient().get()
83+
Mono<String> monoParamD = webClient.get()
9384
.uri("https://temporeal.pbh.gov.br/?param=D")
9485
.retrieve()
95-
.bodyToMono(String.class);
86+
.bodyToMono(String.class)
87+
.onErrorResume(e -> {
88+
logger.error("Erro ao buscar coordenadas D", e);
89+
return Mono.just("[]");
90+
});
9691

97-
Mono<String> monoParamSD = webClientConfig.webClient().get()
92+
Mono<String> monoParamSD = webClient.get()
9893
.uri("https://temporeal.pbh.gov.br/?param=SD")
9994
.retrieve()
100-
.bodyToMono(String.class);
95+
.bodyToMono(String.class)
96+
.onErrorResume(e -> {
97+
logger.error("Erro ao buscar coordenadas SD", e);
98+
return Mono.just("[]");
99+
});
101100

102-
return Flux.merge(monoParamD, monoParamSD).collectList().block();
101+
return Flux.mergeSequential(monoParamD, monoParamSD).collectList().block();
103102
}
104103

105104
private List<CoordenadaDTO> processAndReturnCoordenadas(List<String> responses) {

src/main/java/com/dmware/api_onibusbh/services/DicionarioService.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import com.dmware.api_onibusbh.entities.DicionarioEntity;
1818
import com.dmware.api_onibusbh.repositories.DicionarioRepository;
1919

20+
import static net.logstash.logback.argument.StructuredArguments.kv;
21+
2022
@Service
2123
public class DicionarioService {
2224

@@ -48,34 +50,37 @@ public void salvarDicionarioBanco() {
4850

4951
List<DicionarioDTO> listaDados = apiService.getDicionarioAPIBH();
5052
List<DicionarioEntity> dadosExistentes = dicionarioRepository.findAll();
51-
logger.info("Dados existentes no banco de dados: " + dadosExistentes.size());
53+
logger.info("Estado inicial do banco", kv("total_existente", dadosExistentes.size()));
5254

53-
// Mapeia os dados da api para facilitar a comparação
5455
Map<String, DicionarioEntity> dicionarioMap = dadosExistentes.stream()
5556
.collect(Collectors.toMap(DicionarioEntity::getIdDicionario,
5657
dicionarioEntity -> dicionarioEntity));
5758

5859
List<DicionarioDTO> dadosParaSalvar = new ArrayList<>();
5960

60-
// Realiza a comparação, se for diferente, insere no banco de dados
6161
for (DicionarioDTO dicionarioDTO : listaDados) {
6262
DicionarioEntity dicionarioComparacao = dicionarioMap.get(dicionarioDTO.getId());
6363

6464
if (dicionarioComparacao == null) {
65-
logger.info("Novo dado encontrado: " + dicionarioDTO.getNomeArquivo());
65+
logger.info("Novo dicionário identificado",
66+
kv("nome_arquivo", dicionarioDTO.getNomeArquivo()),
67+
kv("tipo_operacao", "insercao"));
6668
dadosParaSalvar.add(dicionarioDTO);
6769
} else if (!dicionarioDTO.getNomeArquivo().equals(dicionarioComparacao.getNomeArquivo())) {
68-
logger.info("Novo dado atualizado: " + dicionarioDTO.getNomeArquivo());
70+
logger.info("Dicionário atualizado identificado",
71+
kv("nome_arquivo", dicionarioDTO.getNomeArquivo()),
72+
kv("tipo_operacao", "atualizacao"));
6973
dadosParaSalvar.add(dicionarioDTO);
7074
}
7175
}
7276

73-
// Salva apenas os dados que forem novos ou atualizados
7477
if (!dadosParaSalvar.isEmpty()) {
75-
logger.info("Dicionário novo ou atualizados: " + dadosParaSalvar.size());
7678
dicionarioRepository
7779
.saveAll(modelMapper.map(dadosParaSalvar, new TypeToken<List<DicionarioEntity>>() {
7880
}.getType()));
81+
logger.info("Sincronização de dicionário concluída", kv("total_processado", dadosParaSalvar.size()));
82+
} else {
83+
logger.info("Nenhuma alteração necessária no dicionário");
7984
}
8085
}
8186

src/main/java/com/dmware/api_onibusbh/services/LinhasService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.util.Optional;
1919
import java.util.stream.Collectors;
2020

21+
import static net.logstash.logback.argument.StructuredArguments.kv;
22+
2123
@Service
2224
public class LinhasService {
2325

@@ -79,7 +81,7 @@ public void salvaLinhasNormais() {
7981
logger.info("Iniciando sincronização de linhas normais.");
8082
List<LinhaDTO> linhasDaAPI = apiService.getLinhasAPIBH();
8183
List<LinhaEntity> linhasExistentes = linhasRepository.findAll();
82-
logger.info("Linhas existentes no banco: {}", linhasExistentes.size());
84+
logger.info("Estado inicial do banco", kv("total_existente", linhasExistentes.size()));
8385

8486
Map<Integer, LinhaEntity> linhasExistentesMap = linhasExistentes.stream()
8587
.collect(Collectors.toMap(LinhaEntity::getIdLinha, linha -> linha));
@@ -98,7 +100,7 @@ public void salvaLinhasNormais() {
98100

99101
if (!linhasParaSalvar.isEmpty()) {
100102
linhasRepository.saveAll(linhasParaSalvar);
101-
logger.info("Linhas normais sincronizadas com sucesso. Total de linhas salvas/atualizadas: {}", linhasParaSalvar.size());
103+
logger.info("Linhas normais sincronizadas com sucesso", kv("total_sincronizado", linhasParaSalvar.size()));
102104
} else {
103105
logger.info("Nenhuma linha nova ou atualizada encontrada para sincronização.");
104106
}

0 commit comments

Comments
 (0)