Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
473 changes: 303 additions & 170 deletions README.md

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ buildscript {
lombokVersion = '1.18.38'
junitVersion = '5.12.2'
reactorVersion = '3.7.6'
jacksonDatabindVersion = '2.19.0'
log4jVersion = '2.24.3'
fasterxmlJacksonCoreVersion = '2.21.1'
toolsJacksonCoreVersion = '3.1.0'
log4jVersion = '2.25.4'
commonslang3= '3.18.0'
nettyVersion = '4.1.127.Final'
logbackVersion = '1.5.19'
tomcatEmbedCoreVersion = "10.1.47"
nettyVersion = '4.1.132.Final'
netty2Version = '4.2.11.Final'
logbackVersion = '1.5.25'
tomcatEmbedCoreVersion = "10.1.54"
assertjCoreVersion = "3.27.7"
springWebfluxVersion = "7.0.6"
springWebmvcVersion = "7.0.6"
}

}

plugins {
Expand Down
32 changes: 31 additions & 1 deletion dependencyMgmt.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
configurations.configureEach {

/**
* -------------------------------
* Forzado de versiones
Expand All @@ -15,12 +14,43 @@ configurations.configureEach {
}
if (details.requested.group == 'io.netty' && details.requested.name == 'netty-codec-http') {
details.useVersion("${nettyVersion}")
details.because("CVE-2025-67735, CVE-2026-33870")
}
if (details.requested.group == 'io.netty' && details.requested.name == 'netty-codec-http2') {
details.useVersion("${netty2Version}")
details.because("CVE-2026-33871")
}
if (details.requested.group == 'ch.qos.logback') {
details.useVersion("${logbackVersion}")
details.because("CVE-2026-1225 fix")
}
if (details.requested.group == 'org.apache.tomcat.embed' && details.requested.name == 'tomcat-embed-core') {
details.useVersion("${tomcatEmbedCoreVersion}")
details.because("CVE-2025-66614, CVE-2026-24733, CVE-2026-24734")
}
if (details.requested.group == 'org.apache.logging.log4j' && details.requested.name == 'log4j-core') {
details.useVersion("${log4jVersion}")
details.because("CVE-2025-68161 fix")
}
if (details.requested.group == 'com.fasterxml.jackson.core' && details.requested.name == 'jackson-core') {
details.useVersion("${fasterxmlJacksonCoreVersion}")
details.because("GHSA-72hv-8253-57qq")
}
if (details.requested.group == 'tools.jackson.core' && details.requested.name == 'jackson-core') {
details.useVersion("${toolsJacksonCoreVersion}")
details.because("CVE-2026-29062, GHSA-72hv-8253-57qq")
}
if (details.requested.group == 'org.assertj' && details.requested.name == 'assertj-core') {
details.useVersion("${assertjCoreVersion}")
details.because("CVE-2026-24400")
}
if (details.requested.group == 'org.springframework' && details.requested.name == 'spring-webflux') {
details.useVersion("${springWebfluxVersion}")
details.because("CVE-2026-22737, CVE-2026-22735")
}
if (details.requested.group == 'org.springframework' && details.requested.name == 'spring-webmvc') {
details.useVersion("${springWebmvcVersion}")
details.because("CVE-2026-22737, CVE-2026-22735")
}
}
}
7 changes: 6 additions & 1 deletion ecs-core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
dependencies {
// Core dependencies
api project(':ecs-model')
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}"
implementation "com.fasterxml.jackson.core:jackson-databind:${fasterxmlJacksonCoreVersion}"
implementation "org.apache.logging.log4j:log4j-core:${log4jVersion}"

// Spring configuration
implementation("org.springframework.boot:spring-boot")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation "org.springframework:spring-web"
implementation "io.projectreactor:reactor-core"
implementation "io.micrometer:context-propagation"
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:${springBootVersion}")
}

ext {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
package co.com.bancolombia.ecs.domain.middleware;


import co.com.bancolombia.ecs.application.LoggerEcs;
import co.com.bancolombia.ecs.domain.model.AbstractMiddlewareEcsLog;
import co.com.bancolombia.ecs.infra.config.managementid.application.MessageIdMngUseCase;
import co.com.bancolombia.ecs.domain.model.LogRecord;
import co.com.bancolombia.ecs.model.management.BusinessExceptionECS;

public class MiddlewareEcsBusiness extends AbstractMiddlewareEcsLog {
private AbstractMiddlewareEcsLog next;

@Override
public void process(Object request,
String service) {
public void process(Object request, String service) {

if (request instanceof BusinessExceptionECS exp) {

LogRecord.ErrorLog<String, String> optionalMap = new LogRecord.ErrorLog<>();
optionalMap.setOptionalInfo(exp.getOptionalInfo());

var messageId = exp.getMetaInfo().getMessageId();
String messageId = MessageIdMngUseCase.resolveForException(
exp.getMetaInfo().getMessageId()
);

var logError = new LogRecord.ErrorLog<String, String>();
logError.setOptionalInfo(optionalMap.getOptionalInfo());
logError.setOptionalInfo(exp.getOptionalInfo());
logError.setDescription(exp.getConstantBusinessException().getInternalMessage());
logError.setMessage(exp.getConstantBusinessException().getMessage());
logError.setType(exp.getConstantBusinessException().getLogCode());
Expand All @@ -30,7 +28,9 @@ public void process(Object request,
logExp.setError(logError);
logExp.setLevel(LogRecord.Level.ERROR);
logExp.setService(service);
logExp.setMessageId(messageId);
if (messageId != null) {
logExp.setMessageId(messageId);
}

LoggerEcs.print(logExp);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import co.com.bancolombia.ecs.application.LoggerEcs;
import co.com.bancolombia.ecs.domain.model.AbstractMiddlewareEcsLog;
import co.com.bancolombia.ecs.infra.config.managementid.application.MessageIdMngUseCase;
import co.com.bancolombia.ecs.domain.model.LogRecord;

public class MiddlewareEcsExcp extends AbstractMiddlewareEcsLog {
Expand All @@ -23,6 +24,8 @@ public void process(Object request,
logExp.setLevel(LogRecord.Level.ERROR);
logExp.setService(service);

MessageIdMngUseCase.getFromContext().ifPresent(logExp::setMessageId);

LoggerEcs.print(logExp);
} else if (next != null) {
next.handler(request, service);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package co.com.bancolombia.ecs.domain.model;

import lombok.Getter;

@Getter
public enum ExceptionLevel {
BUSINESS_EXCEPTION_ECS("BusinessExceptionECS"),
EXCEPTION("Exception"),
THROWABLE("Throwable");

private final String levelString;

ExceptionLevel(String levelString) {
this.levelString = levelString;
}

public static ExceptionLevel toExceptionLevelIgnoreCase(String value) {
if (value != null) {
for (ExceptionLevel exceptionLevel : ExceptionLevel.values()) {
if (exceptionLevel.levelString.equalsIgnoreCase(value.trim())) {
return exceptionLevel;
}
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package co.com.bancolombia.ecs.domain.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
Expand All @@ -14,6 +15,8 @@
import java.util.Map;
import java.util.UUID;

import static aQute.bnd.annotation.headers.Category.json;


@Data
@AllArgsConstructor
Expand All @@ -23,8 +26,9 @@
public class LogRecord<T, R> {

public static final String DATE_FORMAT = "dd/MM/yyyy HH:mm:ss:SSSS";

public static final String MESSAGE_ID = "message-id";
@Builder.Default
@JsonProperty(MESSAGE_ID)
private String messageId = UUID.randomUUID().toString();
@Builder.Default
private String date = currentDate();
Expand All @@ -38,7 +42,6 @@ private static String currentDate() {
var date = LocalDateTime.now(ZoneOffset.of("-05:00"));
return date.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
}


public String toJson() {
var objectMapper = new ObjectMapper();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.experimental.UtilityClass;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -34,23 +35,34 @@ public static String sanitize(String body, Set<String> sensitiveFields, List<Pat
}
}

// This method handles mixed case headers and symbols '-' and '_' in the header.
public static Map<String, String> sanitizeHeaders(
Set<Map.Entry<String, List<String>>> requestHeaders, Set<String> allowedHeaders) {

Set<String> allowedLower = allowedHeaders.stream()
.map(String::toLowerCase)
.collect(Collectors.toSet());

return requestHeaders.stream()
.filter(entry -> {
String lowerKey = entry.getKey().toLowerCase();
List<String> values = entry.getValue();
return allowedLower.contains(lowerKey) && !values.isEmpty();
})
Map<String, String> allowedHeadersNormalizedToCanonical = allowedHeaders.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().get(0)
DataSanitizer::normalizeKey,
String::toLowerCase,
(first, second) -> first
));

Map<String, String> result = new HashMap<>();
for (Map.Entry<String, List<String>> entry : requestHeaders) {
List<String> values = entry.getValue();
if (values.isEmpty()) {
continue;
}
String normalized = normalizeKey(entry.getKey());
String canonical = allowedHeadersNormalizedToCanonical.get(normalized);
if (canonical != null) {
result.putIfAbsent(canonical, values.getFirst());
}
}
return result;
}

private static String normalizeKey(String key) {
return key.replace("-", "").replace("_", "").toLowerCase();
}

private static void sanitizeJsonMap(Map<String, Object> map, Set<String> sensitiveFields, String replacement) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package co.com.bancolombia.ecs.helpers;

import co.com.bancolombia.ecs.domain.model.ExceptionLevel;
import co.com.bancolombia.ecs.infra.config.PrintOnErrorProperties;
import co.com.bancolombia.ecs.model.management.BusinessExceptionECS;
import co.com.bancolombia.ecs.model.request.LogRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.experimental.UtilityClass;
import org.springframework.http.HttpStatus;
import org.springframework.web.ErrorResponse;
import org.springframework.web.server.ResponseStatusException;

import java.util.Map;
import java.util.Objects;
import java.util.Set;

@UtilityClass
public class HandlerHelper {
public static final String ROOT_PATH = "/";
public static final String RAW_BODY = "raw";
public static final int MIN_ERROR_STATUS_CODE = 400;
public static final Set<String> CONSUMER_ACRONYMS = Set.of("consumer-acronym", "code", "channel");
public static final ObjectMapper objectMapper = new ObjectMapper();

public static void setConsumer(LogRequest logRequest, Map<String, String> headers) {
String consumer = CONSUMER_ACRONYMS.stream()
.map(headers::get)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
logRequest.setConsumer(consumer);
}

public static HttpStatus resolveHttpStatus(Throwable ex) {
if (ex instanceof BusinessExceptionECS businessExceptionECS) {
HttpStatus resolvedStatus = resolveStatusCode(
businessExceptionECS.getConstantBusinessException() != null
? businessExceptionECS.getConstantBusinessException().getStatus()
: null
);
if (resolvedStatus != null) {
return resolvedStatus;
}
}
if (ex instanceof ErrorResponse errorResponse) {
HttpStatus resolvedStatus = resolveStatusCode(errorResponse.getStatusCode().value());
if (resolvedStatus != null) {
return resolvedStatus;
}
}
return HttpStatus.INTERNAL_SERVER_ERROR;
}

public static HttpStatus resolveStatusCode(Integer statusCode) {
return statusCode != null
? HttpStatus.resolve(statusCode)
: null;
}

public static Map<String, String> parseToMap(String body) {
try {
return objectMapper.readValue(body, Map.class);
} catch (JsonProcessingException e) {
return Map.of(RAW_BODY, body);
}
}

public static boolean isErrorStatusCode(int statusCode) {
return statusCode >= MIN_ERROR_STATUS_CODE;
}

public boolean isPathExcluded(String path, Set<String> excludedPaths) {
return ROOT_PATH.equals(path)
|| (excludedPaths != null && excludedPaths.stream().anyMatch(path::startsWith));
}

public boolean errorDoesntMatchLevel(Throwable error, ExceptionLevel exceptionLevel) {
return !PrintOnErrorProperties.matchesConfiguredLevel(error, exceptionLevel);
}

public static ResponseStatusException buildStatusException(HttpStatus status) {
return new ResponseStatusException(status, status.getReasonPhrase());
}
}
Loading
Loading