From c6c58e5b199f0db54216800125736b34a418433a Mon Sep 17 00:00:00 2001 From: mrodriguez Date: Thu, 5 Jun 2025 10:53:28 -0300 Subject: [PATCH 01/24] Add custom JSON logging support with unescaped `data` and plain `message` fields - Ensured `data` field is unescaped JSON, while `message` remains a simple String - Updated `EcsLayout.json` to include `message`, `data`, and `context` fields (or add a new template) - `context` is populated via MDC (`ThreadContext.put`) and supports simple key-value pairs --- .../java/com/genexus/diagnostics/UserLog.java | 13 + .../com/genexus/diagnostics/core/ILogger.java | 12 +- wrappercommon/pom.xml | 20 +- .../core/provider/Log4J2Logger.java | 226 +++++++++++++++++- 4 files changed, 263 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/com/genexus/diagnostics/UserLog.java b/common/src/main/java/com/genexus/diagnostics/UserLog.java index 210041433..45dce93b8 100644 --- a/common/src/main/java/com/genexus/diagnostics/UserLog.java +++ b/common/src/main/java/com/genexus/diagnostics/UserLog.java @@ -120,4 +120,17 @@ public static void debug(String message, String topic) { public static void debug(String message, String topic, Throwable ex) { getLogger(topic).debug(message, ex); } + + + /******* Log Improvements *****/ + + public static void setContext(String key, Object value) { + // Topic is ignored, also if you put something + getLogger("$").setContext(key, value); + } + + public static void write(String message, String topic, int logLevel, Object data, boolean stackTrace) { + getLogger(topic).write(message, logLevel, data, stackTrace); + } + } diff --git a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java index 79424c5be..dd38624e2 100644 --- a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java +++ b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java @@ -65,5 +65,15 @@ public interface ILogger { * msg); } } */ - + + /******* START - Log Improvements *****/ + // A default implementation is added for AndroidLogger because it fails, it needs an + // implementation, another solution is to declare the class as abstract, but it is + // not possible because of the way the class is made. + + default void setContext(String key, Object value) {} + + default void write(String message, int logLevel, Object data, boolean stackTrace) {} + + /******* END - Log Improvements *****/ } diff --git a/wrappercommon/pom.xml b/wrappercommon/pom.xml index b52622955..4f1be37d2 100644 --- a/wrappercommon/pom.xml +++ b/wrappercommon/pom.xml @@ -38,8 +38,24 @@ org.apache.ws.security wss4j 1.6.19 - - + + + com.google.code.gson + gson + 2.12.1 + + + com.genexus + gxclassR + 104.6-trunk.20240524121701-SNAPSHOT + compile + + + org.apache.logging.log4j + log4j-layout-template-json + 2.24.3 + + gxwrappercommon diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 9db36cb9a..445b97353 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -1,6 +1,22 @@ package com.genexus.diagnostics.core.provider; +import com.genexus.diagnostics.LogLevel; import com.genexus.diagnostics.core.ILogger; +import com.genexus.GxUserType; +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout; +import org.apache.logging.log4j.message.ObjectMessage; + +import java.lang.reflect.Type; +import java.util.*; public class Log4J2Logger implements ILogger { private org.apache.logging.log4j.Logger log; @@ -37,7 +53,7 @@ public void fatal(Throwable ex, String[] list) { } public void fatal(String[] list) { - fatal(null, list); + fatal((Throwable) null, list); } public void error(String msg, Throwable ex) { @@ -59,7 +75,7 @@ public void error(Throwable ex, String[] list) { } public void error(String[] list) { - error(null, list); + error((Throwable) null, list); } public void error(String msg) { @@ -80,7 +96,7 @@ public void warn(Throwable ex, String[] list) { } public void warn(String[] list) { - warn(null, list); + warn((Throwable) null, list); } public void warn(String msg, Throwable ex) { @@ -106,7 +122,7 @@ public void debug(Throwable ex, String[] list) { } public void debug(String[] list) { - debug(null, list); + debug((Throwable) null, list); } // Lambda Functions not supported JAVA 7. Only Java 8. @@ -162,7 +178,7 @@ public void trace(Throwable ex, String[] list) { } public void trace(String[] list) { - trace(null, list); + trace((Throwable) null, list); } // Lambda Functions not supported JAVA 7. Only Java 8. @@ -190,4 +206,204 @@ public boolean isErrorEnabled() { return log.isErrorEnabled(); } + + + + + + + /******** NEW methods to improve logging ********/ + + public void setContext(String key, Object value) { + // Add entry to the MDC (only works for JSON log format) + ThreadContext.put(key, fromObjectToString(value)); + } + + private ObjectMessage buildLogMessage(String messageKey, Object messageValue, boolean stackTrace) { + Map messageMap; + String stacktraceLabel = "stackTrace"; + String msgLabel = "message"; + + if (isNullOrBlank(messageValue)) { + if (stackTrace) { + messageMap = new LinkedHashMap<>(); + messageMap.put(msgLabel, messageKey); + messageMap.put(stacktraceLabel, getStackTraceAsList()); + if(isJsonLogFormat()) + return new ObjectMessage(messageMap); + else + return new ObjectMessage(new Gson().toJson(messageMap)); + } + return new ObjectMessage(messageKey); + } else { + messageMap = objectToMap(messageKey, messageValue); + if (stackTrace) { + messageMap.put(stacktraceLabel, getStackTraceAsList()); + } + if(isJsonLogFormat()) + return new ObjectMessage(messageMap); + else + return new ObjectMessage(new Gson().toJson(messageMap)); + } + } + + public void write(String message, int logLevel, Object data, boolean stackTrace) { + printLog("data", data, stackTrace, Level.DEBUG); + } + + private void printLog(final String messageKey, final Object messageValue, final boolean stackTrace, + final Level logLevel) { + + /* Generate the message JSON in this format: + * { "message" : + * { + * "messageKey": "USER messageValue", + * } + * } + * */ + ObjectMessage om = buildLogMessage(messageKey, messageValue, stackTrace); + + // Log the message received or the crafted msg + if (logLevel.equals(Level.FATAL)) log.fatal(om); + else if (logLevel.equals(Level.ERROR)) log.error(om); + else if (logLevel.equals(Level.WARN)) log.warn(om); + else if (logLevel.equals(Level.INFO)) log.info(om); + else if (logLevel.equals(Level.DEBUG)) log.debug(om); + else if (logLevel.equals(Level.TRACE)) log.trace(om); + } + + + + private static String fromObjectToString(Object value) { + String res = ""; + if (value == null) { + res = "null"; + } else if (value instanceof String && isJson((String) value)) { + // Avoid double serialization + res = (String) value; + } else if (value instanceof String) { + res = (String) value; + } else if (value instanceof Number || value instanceof Boolean) { + res = value.toString(); + } else if (value instanceof Map || value instanceof List) { + res = new Gson().toJson(value); + } else if (value instanceof GxUserType) { + res = ((GxUserType) value).toJSonString(); + } else { + // Any other object → serialize as JSON + res = new Gson().toJson(value); + } + return res; + } + + private static boolean isJson(String input) { + try { + JsonElement json = JsonParser.parseString(input); + return json.isJsonObject() || json.isJsonArray(); + } catch (Exception e) { + return false; + } + } + + private Map objectToMap(String key, Object value) { + Map result = new LinkedHashMap<>(); + if (value == null) { + result.put(key, null); + } else if (value instanceof Number || value instanceof Boolean + || value instanceof Map || value instanceof List) { + result.put(key, value); + } else if (value instanceof GxUserType) { + result.put(key, jsonStringToMap(((GxUserType) value).toJSonString())); + } else if (value instanceof String) { + String str = (String) value; + + // Try to parse as JSON + try { + JsonElement parsed = JsonParser.parseString(str); + Gson gson = new Gson(); + if (parsed.isJsonObject()) { + result.put(key, gson.fromJson(parsed, Map.class)); + } else if (parsed.isJsonArray()) { + result.put(key, gson.fromJson(parsed, List.class)); + } else if (parsed.isJsonPrimitive()) { + JsonPrimitive primitive = parsed.getAsJsonPrimitive(); + if (primitive.isBoolean()) { + result.put(key, primitive.getAsBoolean()); + } else if (primitive.isNumber()) { + result.put(key, primitive.getAsNumber()); + } else if (primitive.isString()) { + result.put(key, primitive.getAsString()); + } + } + } catch (JsonSyntaxException e) { + // Invalid JSON: it is left as string + result.put(key, str); + } + } else { + // Any other object: convert to string + result.put(key, value.toString()); + } + return result; + } + + private static String getStackTrace() { + StringBuilder stackTrace; + stackTrace = new StringBuilder(); + for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { + stackTrace.append(ste).append(System.lineSeparator()); + } + return stackTrace.toString(); + } + + private static List getStackTraceAsList() { + List stackTraceLines = new ArrayList<>(); + for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { + stackTraceLines.add(ste.toString()); + } + return stackTraceLines; + } + + private static String stackTraceListToString(List stackTraceLines) { + return String.join(System.lineSeparator(), stackTraceLines); + } + + // Convert a JSON String to Map + private static Map jsonStringToMap(String jsonString) { + Gson gson = new Gson(); + Type type = new TypeToken>(){}.getType(); + return gson.fromJson(jsonString, type); + } + + private String toJson(String key, Object value) { + Map map = new HashMap<>(); + map.put(key, value); + return new Gson().toJson(map); + } + + private static boolean isJsonLogFormat() { + LoggerContext context = (LoggerContext) LogManager.getContext(false); + Configuration config = context.getConfiguration(); + + for (Appender appender : config.getAppenders().values()) { + if (appender instanceof AbstractAppender) { + Object layout = ((AbstractAppender) appender).getLayout(); + if (layout instanceof JsonTemplateLayout) { + return true; + } + } + } + + return false; + } + + public static boolean isNullOrBlank(Object obj) { + if (obj == null) { + return true; + } + if (obj instanceof String) { + return ((String) obj).trim().isEmpty(); + } + return false; // It is not null, and it isn't an empty string + } + } From 0390affd312401a2e6fe69d4b5b413ca99844787 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Fri, 6 Jun 2025 12:00:53 -0300 Subject: [PATCH 02/24] Restore some changes --- .../core/provider/Log4J2Logger.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 445b97353..0659158a3 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -53,7 +53,7 @@ public void fatal(Throwable ex, String[] list) { } public void fatal(String[] list) { - fatal((Throwable) null, list); + fatal(null, list); } public void error(String msg, Throwable ex) { @@ -75,7 +75,7 @@ public void error(Throwable ex, String[] list) { } public void error(String[] list) { - error((Throwable) null, list); + error(null, list); } public void error(String msg) { @@ -96,7 +96,7 @@ public void warn(Throwable ex, String[] list) { } public void warn(String[] list) { - warn((Throwable) null, list); + warn(null, list); } public void warn(String msg, Throwable ex) { @@ -122,7 +122,7 @@ public void debug(Throwable ex, String[] list) { } public void debug(String[] list) { - debug((Throwable) null, list); + debug(null, list); } // Lambda Functions not supported JAVA 7. Only Java 8. @@ -178,7 +178,7 @@ public void trace(Throwable ex, String[] list) { } public void trace(String[] list) { - trace((Throwable) null, list); + trace(null, list); } // Lambda Functions not supported JAVA 7. Only Java 8. @@ -219,6 +219,14 @@ public void setContext(String key, Object value) { ThreadContext.put(key, fromObjectToString(value)); } + public void write(String message, int logLevel, Object data, boolean stackTrace) { + printLog("data", data, stackTrace, Level.DEBUG); + } + + public void write(String message, int logLevel, Object data) { + printLog("data", data, false, Level.DEBUG); + } + private ObjectMessage buildLogMessage(String messageKey, Object messageValue, boolean stackTrace) { Map messageMap; String stacktraceLabel = "stackTrace"; @@ -247,9 +255,7 @@ private ObjectMessage buildLogMessage(String messageKey, Object messageValue, bo } } - public void write(String message, int logLevel, Object data, boolean stackTrace) { - printLog("data", data, stackTrace, Level.DEBUG); - } + private void printLog(final String messageKey, final Object messageValue, final boolean stackTrace, final Level logLevel) { From 9fe1f5de04995cc692281ead4b476f505e2e9257 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Thu, 12 Jun 2025 16:00:43 -0300 Subject: [PATCH 03/24] New methods added --- .../com/genexus/diagnostics/LogLevel.java | 15 +- .../java/com/genexus/diagnostics/UserLog.java | 40 +++- .../com/genexus/diagnostics/core/ILogger.java | 19 +- .../core/provider/Log4J2Logger.java | 185 +++++++----------- 4 files changed, 130 insertions(+), 129 deletions(-) diff --git a/common/src/main/java/com/genexus/diagnostics/LogLevel.java b/common/src/main/java/com/genexus/diagnostics/LogLevel.java index 8279a7835..59890af7c 100644 --- a/common/src/main/java/com/genexus/diagnostics/LogLevel.java +++ b/common/src/main/java/com/genexus/diagnostics/LogLevel.java @@ -2,13 +2,12 @@ public class LogLevel { - static final int OFF = 0; - static final int TRACE = 1; - static final int DEBUG = 5; - static final int INFO = 10; - static final int WARNING = 15; - static final int ERROR = 20; - static final int FATAL = 30; - + public static final int OFF = 0; + public static final int TRACE = 1; + public static final int DEBUG = 5; + public static final int INFO = 10; + public static final int WARNING = 15; + public static final int ERROR = 20; + public static final int FATAL = 30; } diff --git a/common/src/main/java/com/genexus/diagnostics/UserLog.java b/common/src/main/java/com/genexus/diagnostics/UserLog.java index 45dce93b8..4c1edd5b6 100644 --- a/common/src/main/java/com/genexus/diagnostics/UserLog.java +++ b/common/src/main/java/com/genexus/diagnostics/UserLog.java @@ -121,9 +121,6 @@ public static void debug(String message, String topic, Throwable ex) { getLogger(topic).debug(message, ex); } - - /******* Log Improvements *****/ - public static void setContext(String key, Object value) { // Topic is ignored, also if you put something getLogger("$").setContext(key, value); @@ -133,4 +130,41 @@ public static void write(String message, String topic, int logLevel, Object data getLogger(topic).write(message, logLevel, data, stackTrace); } + public static void write(String message, String topic, int logLevel, Object data) { + getLogger(topic).write(message, logLevel, data, false); + } + + public static boolean isDebugEnabled() { + return getLogger().isDebugEnabled(); + } + + public static boolean isErrorEnabled() { + return getLogger().isErrorEnabled(); + } + + public static boolean isFatalEnabled() { + return getLogger().isFatalEnabled(); + } + + public static boolean isInfoEnabled() { + return getLogger().isInfoEnabled(); + } + + public static boolean isWarnEnabled() { + return getLogger().isWarnEnabled(); + } + + public static boolean isTraceEnabled() { + return getLogger().isTraceEnabled(); + } + + public static boolean isEnabled(int logLevel) { + return getLogger().isEnabled(logLevel); + } + + public static boolean isEnabled(int logLevel, String topic) { + return getLogger(topic).isEnabled(logLevel); + } + + } diff --git a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java index dd38624e2..478fbfa14 100644 --- a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java +++ b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java @@ -65,15 +65,20 @@ public interface ILogger { * msg); } } */ + void setContext(String key, Object value); - /******* START - Log Improvements *****/ - // A default implementation is added for AndroidLogger because it fails, it needs an - // implementation, another solution is to declare the class as abstract, but it is - // not possible because of the way the class is made. + void write(String message, int logLevel, Object data, boolean stackTrace); - default void setContext(String key, Object value) {} + boolean isFatalEnabled(); - default void write(String message, int logLevel, Object data, boolean stackTrace) {} + boolean isWarnEnabled(); + + boolean isInfoEnabled(); + + boolean isTraceEnabled(); + + boolean isEnabled(int logLevel); + + //boolean isEnabled(int logLevel, String topic); - /******* END - Log Improvements *****/ } diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 0659158a3..ad2f44082 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -7,13 +7,14 @@ import com.google.gson.reflect.TypeToken; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout; -import org.apache.logging.log4j.message.ObjectMessage; +import org.apache.logging.log4j.message.MapMessage; import java.lang.reflect.Type; import java.util.*; @@ -206,13 +207,29 @@ public boolean isErrorEnabled() { return log.isErrorEnabled(); } + public boolean isFatalEnabled() { + return log.isFatalEnabled(); + } + public boolean isWarnEnabled() { + return log.isWarnEnabled(); + } + public boolean isInfoEnabled() { + return log.isInfoEnabled(); + } + public boolean isTraceEnabled() { + return log.isTraceEnabled(); + } + public boolean isEnabled(int logLevel) { + return log.isEnabled(getLogLevel(logLevel)); + } - - /******** NEW methods to improve logging ********/ + public boolean isEnabled(int logLevel, String marker) { + return log.isEnabled(getLogLevel(logLevel), MarkerManager.getMarker(marker)); + } public void setContext(String key, Object value) { // Add entry to the MDC (only works for JSON log format) @@ -220,68 +237,72 @@ public void setContext(String key, Object value) { } public void write(String message, int logLevel, Object data, boolean stackTrace) { - printLog("data", data, stackTrace, Level.DEBUG); - } + if (isJsonLogFormat()) + writeJsonFormat(message, logLevel, data, stackTrace); + else + writeTextFormat(message, logLevel, data, stackTrace); + } + + private void writeTextFormat(String message, int logLevel, Object data, boolean stackTrace) { + String dataKey = "data"; + Map mapMessage = new LinkedHashMap<>(); + + if (data == null || (data instanceof String && "null".equals(data.toString()))) { + mapMessage.put(dataKey, (Object) null); + } else if (data instanceof GxUserType) { // SDT + mapMessage.put(dataKey, jsonStringToMap(fromObjectToString(data))); + } else if (data instanceof String && isJson((String) data)) { // JSON Strings + mapMessage.put(dataKey, jsonStringToMap(fromObjectToString(data))); + } else { + mapMessage.put(dataKey, data); + } - public void write(String message, int logLevel, Object data) { - printLog("data", data, false, Level.DEBUG); - } + if (stackTrace) { + mapMessage.put("stackTrace", getStackTraceAsList()); + } - private ObjectMessage buildLogMessage(String messageKey, Object messageValue, boolean stackTrace) { - Map messageMap; - String stacktraceLabel = "stackTrace"; - String msgLabel = "message"; + String json = new Gson().newBuilder().serializeNulls().create().toJson(mapMessage); + String format = "{} - {}"; - if (isNullOrBlank(messageValue)) { - if (stackTrace) { - messageMap = new LinkedHashMap<>(); - messageMap.put(msgLabel, messageKey); - messageMap.put(stacktraceLabel, getStackTraceAsList()); - if(isJsonLogFormat()) - return new ObjectMessage(messageMap); - else - return new ObjectMessage(new Gson().toJson(messageMap)); - } - return new ObjectMessage(messageKey); - } else { - messageMap = objectToMap(messageKey, messageValue); - if (stackTrace) { - messageMap.put(stacktraceLabel, getStackTraceAsList()); - } - if(isJsonLogFormat()) - return new ObjectMessage(messageMap); - else - return new ObjectMessage(new Gson().toJson(messageMap)); - } - } + log.log(getLogLevel(logLevel), format, message, json); + } + private void writeJsonFormat(String message, int logLevel, Object data, boolean stackTrace) { + String dataKey = "data"; + MapMessage mapMessage = new MapMessage<>().with("message", message); - private void printLog(final String messageKey, final Object messageValue, final boolean stackTrace, - final Level logLevel) { + if (data == null || (data instanceof String && "null".equals(data.toString()))) { + mapMessage.with(dataKey, (Object) null); + } else if (data instanceof GxUserType) { // SDT + mapMessage.with(dataKey, jsonStringToMap(fromObjectToString(data))); + } else if (data instanceof String && isJson((String) data)) { // JSON Strings + mapMessage.with(dataKey, jsonStringToMap(fromObjectToString(data))); + } else { + mapMessage.with(dataKey, data); + } - /* Generate the message JSON in this format: - * { "message" : - * { - * "messageKey": "USER messageValue", - * } - * } - * */ - ObjectMessage om = buildLogMessage(messageKey, messageValue, stackTrace); + if (stackTrace) { + mapMessage.with("stackTrace", getStackTraceAsList()); + } - // Log the message received or the crafted msg - if (logLevel.equals(Level.FATAL)) log.fatal(om); - else if (logLevel.equals(Level.ERROR)) log.error(om); - else if (logLevel.equals(Level.WARN)) log.warn(om); - else if (logLevel.equals(Level.INFO)) log.info(om); - else if (logLevel.equals(Level.DEBUG)) log.debug(om); - else if (logLevel.equals(Level.TRACE)) log.trace(om); + log.log(getLogLevel(logLevel), mapMessage); } - + private Level getLogLevel(int logLevel) { + switch (logLevel) { + case LogLevel.OFF: return Level.OFF; + case LogLevel.TRACE: return Level.TRACE; + case LogLevel.INFO: return Level.INFO; + case LogLevel.WARNING: return Level.WARN; + case LogLevel.ERROR: return Level.ERROR; + case LogLevel.FATAL: return Level.FATAL; + default: return Level.DEBUG; + } + } private static String fromObjectToString(Object value) { - String res = ""; + String res; if (value == null) { res = "null"; } else if (value instanceof String && isJson((String) value)) { @@ -311,47 +332,6 @@ private static boolean isJson(String input) { } } - private Map objectToMap(String key, Object value) { - Map result = new LinkedHashMap<>(); - if (value == null) { - result.put(key, null); - } else if (value instanceof Number || value instanceof Boolean - || value instanceof Map || value instanceof List) { - result.put(key, value); - } else if (value instanceof GxUserType) { - result.put(key, jsonStringToMap(((GxUserType) value).toJSonString())); - } else if (value instanceof String) { - String str = (String) value; - - // Try to parse as JSON - try { - JsonElement parsed = JsonParser.parseString(str); - Gson gson = new Gson(); - if (parsed.isJsonObject()) { - result.put(key, gson.fromJson(parsed, Map.class)); - } else if (parsed.isJsonArray()) { - result.put(key, gson.fromJson(parsed, List.class)); - } else if (parsed.isJsonPrimitive()) { - JsonPrimitive primitive = parsed.getAsJsonPrimitive(); - if (primitive.isBoolean()) { - result.put(key, primitive.getAsBoolean()); - } else if (primitive.isNumber()) { - result.put(key, primitive.getAsNumber()); - } else if (primitive.isString()) { - result.put(key, primitive.getAsString()); - } - } - } catch (JsonSyntaxException e) { - // Invalid JSON: it is left as string - result.put(key, str); - } - } else { - // Any other object: convert to string - result.put(key, value.toString()); - } - return result; - } - private static String getStackTrace() { StringBuilder stackTrace; stackTrace = new StringBuilder(); @@ -380,12 +360,6 @@ private static Map jsonStringToMap(String jsonString) { return gson.fromJson(jsonString, type); } - private String toJson(String key, Object value) { - Map map = new HashMap<>(); - map.put(key, value); - return new Gson().toJson(map); - } - private static boolean isJsonLogFormat() { LoggerContext context = (LoggerContext) LogManager.getContext(false); Configuration config = context.getConfiguration(); @@ -398,18 +372,7 @@ private static boolean isJsonLogFormat() { } } } - return false; } - public static boolean isNullOrBlank(Object obj) { - if (obj == null) { - return true; - } - if (obj instanceof String) { - return ((String) obj).trim().isEmpty(); - } - return false; // It is not null, and it isn't an empty string - } - } From 5757ac42ba80727c892c178f1117b8eae8c0b3b5 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Wed, 18 Jun 2025 14:39:09 -0300 Subject: [PATCH 04/24] A custom resolver is added to handle messages other than MapMessages to be compatible with the existing implementation of the log (the old implementation remains unchanged). --- .../core/provider/CustomMessageFactory.java | 29 +++++++++++++++ .../core/provider/CustomMessageResolver.java | 36 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java create mode 100644 wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageResolver.java diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java new file mode 100644 index 000000000..a1998ebd5 --- /dev/null +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java @@ -0,0 +1,29 @@ +package com.genexus.diagnostics.core.provider; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext; +import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory; + + +@Plugin(name = "CustomMessageFactory", category = TemplateResolverFactory.CATEGORY) +public final class CustomMessageFactory implements EventResolverFactory { + private static final CustomMessageFactory INSTANCE = new CustomMessageFactory(); + + @PluginFactory + public static CustomMessageFactory getInstance() { + return INSTANCE; + } + + @Override + public String getName() { + return CustomMessageResolver.getName(); + } + + @Override + public CustomMessageResolver create(EventResolverContext context, TemplateResolverConfig config) { + return new CustomMessageResolver(config); + } +} diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageResolver.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageResolver.java new file mode 100644 index 000000000..d8541284e --- /dev/null +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageResolver.java @@ -0,0 +1,36 @@ +package com.genexus.diagnostics.core.provider; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.layout.template.json.resolver.EventResolver; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.message.MapMessage; +import org.apache.logging.log4j.message.Message; + + +public class CustomMessageResolver implements EventResolver { + private static final String RESOLVER_NAME = "customMessage"; + + CustomMessageResolver(TemplateResolverConfig config) { + } + + static String getName() { + return RESOLVER_NAME; + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + Message message = logEvent.getMessage(); + if (message instanceof MapMessage) { + MapMessage mapMessage = (MapMessage) message; + Object msgValue = mapMessage.get("message"); + if (msgValue != null) { + jsonWriter.writeString(msgValue.toString()); + return; + } + } + // fallback + jsonWriter.writeString(message.getFormattedMessage()); + } +} + From 14345036504ee2705789380e1a03a4cf3723fe17 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Wed, 18 Jun 2025 14:41:06 -0300 Subject: [PATCH 05/24] Add a custom .json template that uses the custom resolver --- .../core/provider/CustomEcsLayout.json | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomEcsLayout.json diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomEcsLayout.json b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomEcsLayout.json new file mode 100644 index 000000000..bf77e34d6 --- /dev/null +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomEcsLayout.json @@ -0,0 +1,57 @@ +{ + "@timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "timeZone": "UTC" + } + }, + "ecs.version": "1.2.0", + "log.level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "customMessage", + "stringified": true + }, + "data": { + "$resolver": "map", + "key": "data" + }, + "process.thread.name": { + "$resolver": "thread", + "field": "name" + }, + "log.logger": { + "$resolver": "logger", + "field": "name" + }, + "tags": { + "$resolver": "ndc" + }, + "error.type": { + "$resolver": "exception", + "field": "className" + }, + "error.message": { + "$resolver": "exception", + "field": "message" + }, + "error.stack_trace": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + }, + "context": { + "$resolver": "mdc", + "field": "context", + "stringified": false + }, + "stackTrace": { + "$resolver": "map", + "key": "stackTrace" + } +} From 019dd90155f0f5c961cc804faa941e02c1819c14 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Wed, 18 Jun 2025 14:51:18 -0300 Subject: [PATCH 06/24] Refactoring --- .../core/provider/Log4J2Logger.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index ad2f44082..4324fc55b 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -1,9 +1,11 @@ package com.genexus.diagnostics.core.provider; +import com.genexus.GxUserType; import com.genexus.diagnostics.LogLevel; import com.genexus.diagnostics.core.ILogger; -import com.genexus.GxUserType; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; @@ -17,7 +19,10 @@ import org.apache.logging.log4j.message.MapMessage; import java.lang.reflect.Type; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; public class Log4J2Logger implements ILogger { private org.apache.logging.log4j.Logger log; @@ -243,6 +248,9 @@ public void write(String message, int logLevel, Object data, boolean stackTrace) writeTextFormat(message, logLevel, data, stackTrace); } + private static final String STACKTRACE_KEY = "stackTrace"; + private static final String MESSAGE_KEY = "message"; + private void writeTextFormat(String message, int logLevel, Object data, boolean stackTrace) { String dataKey = "data"; Map mapMessage = new LinkedHashMap<>(); @@ -258,19 +266,17 @@ private void writeTextFormat(String message, int logLevel, Object data, boolean } if (stackTrace) { - mapMessage.put("stackTrace", getStackTraceAsList()); + mapMessage.put(STACKTRACE_KEY, getStackTraceAsList()); } String json = new Gson().newBuilder().serializeNulls().create().toJson(mapMessage); String format = "{} - {}"; - log.log(getLogLevel(logLevel), format, message, json); - } private void writeJsonFormat(String message, int logLevel, Object data, boolean stackTrace) { String dataKey = "data"; - MapMessage mapMessage = new MapMessage<>().with("message", message); + MapMessage mapMessage = new MapMessage<>().with(MESSAGE_KEY, message); if (data == null || (data instanceof String && "null".equals(data.toString()))) { mapMessage.with(dataKey, (Object) null); @@ -283,7 +289,7 @@ private void writeJsonFormat(String message, int logLevel, Object data, boolean } if (stackTrace) { - mapMessage.with("stackTrace", getStackTraceAsList()); + mapMessage.with(STACKTRACE_KEY, getStackTraceAsList()); } log.log(getLogLevel(logLevel), mapMessage); From 87c7b43920e18c527145c8f59a5f7bb0e58a599c Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Thu, 26 Jun 2025 12:51:17 -0300 Subject: [PATCH 07/24] Fix cyclic referencing of gxClassR --- wrappercommon/pom.xml | 6 ----- .../core/provider/Log4J2Logger.java | 24 ++----------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/wrappercommon/pom.xml b/wrappercommon/pom.xml index 4f1be37d2..0f17fb910 100644 --- a/wrappercommon/pom.xml +++ b/wrappercommon/pom.xml @@ -44,12 +44,6 @@ gson 2.12.1 - - com.genexus - gxclassR - 104.6-trunk.20240524121701-SNAPSHOT - compile - org.apache.logging.log4j log4j-layout-template-json diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 4324fc55b..892d3b444 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -1,6 +1,5 @@ package com.genexus.diagnostics.core.provider; -import com.genexus.GxUserType; import com.genexus.diagnostics.LogLevel; import com.genexus.diagnostics.core.ILogger; import com.google.gson.Gson; @@ -257,10 +256,8 @@ private void writeTextFormat(String message, int logLevel, Object data, boolean if (data == null || (data instanceof String && "null".equals(data.toString()))) { mapMessage.put(dataKey, (Object) null); - } else if (data instanceof GxUserType) { // SDT - mapMessage.put(dataKey, jsonStringToMap(fromObjectToString(data))); } else if (data instanceof String && isJson((String) data)) { // JSON Strings - mapMessage.put(dataKey, jsonStringToMap(fromObjectToString(data))); + mapMessage.put(dataKey, jsonStringToMap((String)data)); } else { mapMessage.put(dataKey, data); } @@ -280,10 +277,8 @@ private void writeJsonFormat(String message, int logLevel, Object data, boolean if (data == null || (data instanceof String && "null".equals(data.toString()))) { mapMessage.with(dataKey, (Object) null); - } else if (data instanceof GxUserType) { // SDT - mapMessage.with(dataKey, jsonStringToMap(fromObjectToString(data))); } else if (data instanceof String && isJson((String) data)) { // JSON Strings - mapMessage.with(dataKey, jsonStringToMap(fromObjectToString(data))); + mapMessage.with(dataKey, jsonStringToMap((String)data)); } else { mapMessage.with(dataKey, data); } @@ -320,8 +315,6 @@ private static String fromObjectToString(Object value) { res = value.toString(); } else if (value instanceof Map || value instanceof List) { res = new Gson().toJson(value); - } else if (value instanceof GxUserType) { - res = ((GxUserType) value).toJSonString(); } else { // Any other object → serialize as JSON res = new Gson().toJson(value); @@ -338,15 +331,6 @@ private static boolean isJson(String input) { } } - private static String getStackTrace() { - StringBuilder stackTrace; - stackTrace = new StringBuilder(); - for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { - stackTrace.append(ste).append(System.lineSeparator()); - } - return stackTrace.toString(); - } - private static List getStackTraceAsList() { List stackTraceLines = new ArrayList<>(); for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { @@ -355,10 +339,6 @@ private static List getStackTraceAsList() { return stackTraceLines; } - private static String stackTraceListToString(List stackTraceLines) { - return String.join(System.lineSeparator(), stackTraceLines); - } - // Convert a JSON String to Map private static Map jsonStringToMap(String jsonString) { Gson gson = new Gson(); From 466e167c48925257e8807f22fa7c445f243db306 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Thu, 26 Jun 2025 12:59:24 -0300 Subject: [PATCH 08/24] Fix: AndroidLogger is not abstract and does not override abstract method isEnabled --- common/src/main/java/com/genexus/diagnostics/core/ILogger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java index 478fbfa14..b39e44846 100644 --- a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java +++ b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java @@ -77,7 +77,7 @@ public interface ILogger { boolean isTraceEnabled(); - boolean isEnabled(int logLevel); + default boolean isEnabled(int logLevel) { return false; } //boolean isEnabled(int logLevel, String topic); From ca17eb51660646b1792f39616ef2572e2e40e736 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Thu, 26 Jun 2025 13:07:57 -0300 Subject: [PATCH 09/24] Fix: AndroidLogger is not abstract and does not override abstract methods --- .../java/com/genexus/diagnostics/core/ILogger.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java index b39e44846..e5b9b8122 100644 --- a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java +++ b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java @@ -65,17 +65,17 @@ public interface ILogger { * msg); } } */ - void setContext(String key, Object value); + default void setContext(String key, Object value) {} - void write(String message, int logLevel, Object data, boolean stackTrace); + default void write(String message, int logLevel, Object data, boolean stackTrace) {} - boolean isFatalEnabled(); + default boolean isFatalEnabled() { return false; } - boolean isWarnEnabled(); + default boolean isWarnEnabled() { return false; } - boolean isInfoEnabled(); + default boolean isInfoEnabled() { return false; } - boolean isTraceEnabled(); + default boolean isTraceEnabled() { return false; } default boolean isEnabled(int logLevel) { return false; } From 4c6e64c895b72330066256b1cf0e39c1ca50a6b1 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Mon, 30 Jun 2025 15:45:16 -0300 Subject: [PATCH 10/24] Remove unnecessary comment --- common/src/main/java/com/genexus/diagnostics/core/ILogger.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java index e5b9b8122..403c3ad98 100644 --- a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java +++ b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java @@ -79,6 +79,4 @@ default void write(String message, int logLevel, Object data, boolean stackTrace default boolean isEnabled(int logLevel) { return false; } - //boolean isEnabled(int logLevel, String topic); - } From bce840b6ac9452e18c5a09396cce4ba4eb5904d0 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Mon, 30 Jun 2025 15:50:14 -0300 Subject: [PATCH 11/24] Avoid code duplication --- common/src/main/java/com/genexus/diagnostics/UserLog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/com/genexus/diagnostics/UserLog.java b/common/src/main/java/com/genexus/diagnostics/UserLog.java index 4c1edd5b6..411e72312 100644 --- a/common/src/main/java/com/genexus/diagnostics/UserLog.java +++ b/common/src/main/java/com/genexus/diagnostics/UserLog.java @@ -131,7 +131,7 @@ public static void write(String message, String topic, int logLevel, Object data } public static void write(String message, String topic, int logLevel, Object data) { - getLogger(topic).write(message, logLevel, data, false); + write(message, topic, logLevel, data, false); } public static boolean isDebugEnabled() { From f5a2d66b400af60fe14a26309245992342270d5f Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Tue, 1 Jul 2025 10:36:51 -0300 Subject: [PATCH 12/24] Uses org.json instead of Gson --- wrappercommon/pom.xml | 5 -- .../core/provider/Log4J2Logger.java | 75 +++++++++++++------ 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/wrappercommon/pom.xml b/wrappercommon/pom.xml index 0f17fb910..16cb5f022 100644 --- a/wrappercommon/pom.xml +++ b/wrappercommon/pom.xml @@ -39,11 +39,6 @@ wss4j 1.6.19 - - com.google.code.gson - gson - 2.12.1 - org.apache.logging.log4j log4j-layout-template-json diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 892d3b444..2d143aebb 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -2,10 +2,6 @@ import com.genexus.diagnostics.LogLevel; import com.genexus.diagnostics.core.ILogger; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.google.gson.reflect.TypeToken; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.MarkerManager; @@ -16,8 +12,9 @@ import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout; import org.apache.logging.log4j.message.MapMessage; +import org.json.JSONArray; +import org.json.JSONObject; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -255,7 +252,7 @@ private void writeTextFormat(String message, int logLevel, Object data, boolean Map mapMessage = new LinkedHashMap<>(); if (data == null || (data instanceof String && "null".equals(data.toString()))) { - mapMessage.put(dataKey, (Object) null); + mapMessage.put(dataKey, JSONObject.NULL); } else if (data instanceof String && isJson((String) data)) { // JSON Strings mapMessage.put(dataKey, jsonStringToMap((String)data)); } else { @@ -266,7 +263,7 @@ private void writeTextFormat(String message, int logLevel, Object data, boolean mapMessage.put(STACKTRACE_KEY, getStackTraceAsList()); } - String json = new Gson().newBuilder().serializeNulls().create().toJson(mapMessage); + String json = new JSONObject(mapMessage).toString(); String format = "{} - {}"; log.log(getLogLevel(logLevel), format, message, json); } @@ -313,21 +310,28 @@ private static String fromObjectToString(Object value) { res = (String) value; } else if (value instanceof Number || value instanceof Boolean) { res = value.toString(); - } else if (value instanceof Map || value instanceof List) { - res = new Gson().toJson(value); + } else if (value instanceof Map) { + res = new JSONObject((Map) value).toString(); + } else if (value instanceof List) { + res = new JSONArray((List) value).toString(); } else { // Any other object → serialize as JSON - res = new Gson().toJson(value); + res = JSONObject.quote(value.toString()); } return res; } - private static boolean isJson(String input) { + private static boolean isJson(String str) { try { - JsonElement json = JsonParser.parseString(input); - return json.isJsonObject() || json.isJsonArray(); - } catch (Exception e) { - return false; + new JSONObject(str); + return true; + } catch (Exception e1) { + try { + new JSONArray(str); + return true; + } catch (Exception e2) { + return false; + } } } @@ -339,13 +343,6 @@ private static List getStackTraceAsList() { return stackTraceLines; } - // Convert a JSON String to Map - private static Map jsonStringToMap(String jsonString) { - Gson gson = new Gson(); - Type type = new TypeToken>(){}.getType(); - return gson.fromJson(jsonString, type); - } - private static boolean isJsonLogFormat() { LoggerContext context = (LoggerContext) LogManager.getContext(false); Configuration config = context.getConfiguration(); @@ -361,4 +358,38 @@ private static boolean isJsonLogFormat() { return false; } + public static Map jsonStringToMap(String jsonString) { + JSONObject jsonObject = new JSONObject(jsonString); + return toMap(jsonObject); + } + + private static Map toMap(JSONObject jsonObject) { + Map map = new LinkedHashMap<>(); + for (String key : jsonObject.keySet()) { + Object value = jsonObject.get(key); + map.put(key, convert(value)); + } + return map; + } + + private static List toList(JSONArray array) { + List list = new ArrayList<>(); + for (int i = 0; i < array.length(); i++) { + Object value = array.get(i); + list.add(convert(value)); + } + return list; + } + + private static Object convert(Object value) { + if (value instanceof JSONObject) { + return toMap((JSONObject) value); + } else if (value instanceof JSONArray) { + return toList((JSONArray) value); + } else if (value.equals(JSONObject.NULL)) { + return null; + } else { + return value; + } + } } From 9cc80d0e78e653d5784fb150bdddc256dd631aa9 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Tue, 1 Jul 2025 10:39:02 -0300 Subject: [PATCH 13/24] Remove casting --- .../com/genexus/diagnostics/core/provider/Log4J2Logger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 2d143aebb..0773a2d67 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -349,7 +349,7 @@ private static boolean isJsonLogFormat() { for (Appender appender : config.getAppenders().values()) { if (appender instanceof AbstractAppender) { - Object layout = ((AbstractAppender) appender).getLayout(); + Object layout = appender.getLayout(); if (layout instanceof JsonTemplateLayout) { return true; } From ebb9788b9f174e6912293cd59a19e0cd35ef8b42 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Tue, 1 Jul 2025 15:13:18 -0300 Subject: [PATCH 14/24] Ensure type safety --- .../java/com/genexus/diagnostics/Log.java | 20 ++++++------ .../com/genexus/diagnostics/LogLevel.java | 31 +++++++++++++------ .../java/com/genexus/diagnostics/UserLog.java | 20 ++++++------ .../core/provider/Log4J2Logger.java | 15 ++++----- 4 files changed, 47 insertions(+), 39 deletions(-) diff --git a/common/src/main/java/com/genexus/diagnostics/Log.java b/common/src/main/java/com/genexus/diagnostics/Log.java index 4f3476bc0..b5bc65d6f 100644 --- a/common/src/main/java/com/genexus/diagnostics/Log.java +++ b/common/src/main/java/com/genexus/diagnostics/Log.java @@ -29,26 +29,24 @@ public static void write(int logLevel, String message, String topic) { public static void write(String message, String topic, int logLevel) { ILogger log = getLogger(topic); - - switch (logLevel) { - case LogLevel.OFF: //LogLevel off + LogLevel level = LogLevel.fromInt(logLevel); + + switch (level) { + case OFF: //LogLevel off break; - case LogLevel.TRACE: + case TRACE: log.trace(message); break; - case LogLevel.DEBUG: - log.debug(message); - break; - case LogLevel.INFO: + case INFO: log.info(message); break; - case LogLevel.WARNING: + case WARNING: log.warn(message); break; - case LogLevel.ERROR: + case ERROR: log.error(message); break; - case LogLevel.FATAL: + case FATAL: log.fatal(message); break; default: diff --git a/common/src/main/java/com/genexus/diagnostics/LogLevel.java b/common/src/main/java/com/genexus/diagnostics/LogLevel.java index 59890af7c..7ca18a523 100644 --- a/common/src/main/java/com/genexus/diagnostics/LogLevel.java +++ b/common/src/main/java/com/genexus/diagnostics/LogLevel.java @@ -1,13 +1,24 @@ package com.genexus.diagnostics; -public class LogLevel { - - public static final int OFF = 0; - public static final int TRACE = 1; - public static final int DEBUG = 5; - public static final int INFO = 10; - public static final int WARNING = 15; - public static final int ERROR = 20; - public static final int FATAL = 30; - +public enum LogLevel { + OFF(0), + TRACE(1), + DEBUG(5), + INFO(10), + WARNING(15), + ERROR(20), + FATAL(30); + + private final int lvl; + LogLevel(int lvl) { this.lvl = lvl; } + public int intValue() { return lvl; } + + public static LogLevel fromInt(int lvl) { + for (LogLevel level : LogLevel.values()) { + if (level.intValue() == lvl) { + return level; + } + } + return LogLevel.OFF; + } } diff --git a/common/src/main/java/com/genexus/diagnostics/UserLog.java b/common/src/main/java/com/genexus/diagnostics/UserLog.java index 411e72312..59552c877 100644 --- a/common/src/main/java/com/genexus/diagnostics/UserLog.java +++ b/common/src/main/java/com/genexus/diagnostics/UserLog.java @@ -28,28 +28,26 @@ private static ILogger getLogger(String topic) { return log; } - public static void write( int logLevel, String message, String topic) { + public static void write(int logLevel, String message, String topic) { ILogger log = getLogger(topic); + LogLevel level = LogLevel.fromInt(logLevel); - switch (logLevel) { - case LogLevel.OFF: //LogLevel off + switch (level) { + case OFF: //LogLevel off break; - case LogLevel.TRACE: + case TRACE: log.trace(message); break; - case LogLevel.DEBUG: - log.debug(message); - break; - case LogLevel.INFO: + case INFO: log.info(message); break; - case LogLevel.WARNING: + case WARNING: log.warn(message); break; - case LogLevel.ERROR: + case ERROR: log.error(message); break; - case LogLevel.FATAL: + case FATAL: log.fatal(message); break; default: diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 0773a2d67..6bbdb881a 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -288,13 +288,14 @@ private void writeJsonFormat(String message, int logLevel, Object data, boolean } private Level getLogLevel(int logLevel) { - switch (logLevel) { - case LogLevel.OFF: return Level.OFF; - case LogLevel.TRACE: return Level.TRACE; - case LogLevel.INFO: return Level.INFO; - case LogLevel.WARNING: return Level.WARN; - case LogLevel.ERROR: return Level.ERROR; - case LogLevel.FATAL: return Level.FATAL; + LogLevel level = LogLevel.fromInt(logLevel); + switch (level) { + case OFF: return Level.OFF; + case TRACE: return Level.TRACE; + case INFO: return Level.INFO; + case WARNING: return Level.WARN; + case ERROR: return Level.ERROR; + case FATAL: return Level.FATAL; default: return Level.DEBUG; } } From 018e64e658394ffb65b1bc70df09d95aa1da5f3f Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Tue, 1 Jul 2025 15:27:29 -0300 Subject: [PATCH 15/24] Move it to the resources folder. When compiling it will be in the root of the jar --- .../diagnostics/core/provider => resources}/CustomEcsLayout.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename wrappercommon/src/main/{java/com/genexus/diagnostics/core/provider => resources}/CustomEcsLayout.json (100%) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomEcsLayout.json b/wrappercommon/src/main/resources/CustomEcsLayout.json similarity index 100% rename from wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomEcsLayout.json rename to wrappercommon/src/main/resources/CustomEcsLayout.json From dd7a0f85e8b7953869d3ed4c2cf881f75c9daaaa Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Tue, 1 Jul 2025 15:40:47 -0300 Subject: [PATCH 16/24] Rename constant to WARN --- common/src/main/java/com/genexus/diagnostics/Log.java | 4 ++-- common/src/main/java/com/genexus/diagnostics/LogLevel.java | 2 +- common/src/main/java/com/genexus/diagnostics/UserLog.java | 2 +- .../com/genexus/diagnostics/core/provider/Log4J2Logger.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/com/genexus/diagnostics/Log.java b/common/src/main/java/com/genexus/diagnostics/Log.java index b5bc65d6f..a94f15813 100644 --- a/common/src/main/java/com/genexus/diagnostics/Log.java +++ b/common/src/main/java/com/genexus/diagnostics/Log.java @@ -40,7 +40,7 @@ public static void write(String message, String topic, int logLevel) { case INFO: log.info(message); break; - case WARNING: + case WARN: log.warn(message); break; case ERROR: @@ -50,7 +50,7 @@ public static void write(String message, String topic, int logLevel) { log.fatal(message); break; default: - log.debug(message); + log.debug(message); } } diff --git a/common/src/main/java/com/genexus/diagnostics/LogLevel.java b/common/src/main/java/com/genexus/diagnostics/LogLevel.java index 7ca18a523..2b7caf9a1 100644 --- a/common/src/main/java/com/genexus/diagnostics/LogLevel.java +++ b/common/src/main/java/com/genexus/diagnostics/LogLevel.java @@ -5,7 +5,7 @@ public enum LogLevel { TRACE(1), DEBUG(5), INFO(10), - WARNING(15), + WARN(15), ERROR(20), FATAL(30); diff --git a/common/src/main/java/com/genexus/diagnostics/UserLog.java b/common/src/main/java/com/genexus/diagnostics/UserLog.java index 59552c877..f306c88ac 100644 --- a/common/src/main/java/com/genexus/diagnostics/UserLog.java +++ b/common/src/main/java/com/genexus/diagnostics/UserLog.java @@ -41,7 +41,7 @@ public static void write(int logLevel, String message, String topic) { case INFO: log.info(message); break; - case WARNING: + case WARN: log.warn(message); break; case ERROR: diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 6bbdb881a..40fdbe771 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -293,7 +293,7 @@ private Level getLogLevel(int logLevel) { case OFF: return Level.OFF; case TRACE: return Level.TRACE; case INFO: return Level.INFO; - case WARNING: return Level.WARN; + case WARN: return Level.WARN; case ERROR: return Level.ERROR; case FATAL: return Level.FATAL; default: return Level.DEBUG; From 940f24aa22a6262e1dc864e44936558ba33b7328 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Tue, 1 Jul 2025 15:51:07 -0300 Subject: [PATCH 17/24] Explicitly prevent instantiation --- .../genexus/diagnostics/core/provider/CustomMessageFactory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java index a1998ebd5..aae43c5c8 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java @@ -11,6 +11,7 @@ @Plugin(name = "CustomMessageFactory", category = TemplateResolverFactory.CATEGORY) public final class CustomMessageFactory implements EventResolverFactory { private static final CustomMessageFactory INSTANCE = new CustomMessageFactory(); + private CustomMessageFactory() { /* no instances */ } @PluginFactory public static CustomMessageFactory getInstance() { From 3381ea9e31c9c3a8bfbc0833866fae9e92d9709c Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Tue, 1 Jul 2025 15:52:05 -0300 Subject: [PATCH 18/24] Plugin rename --- .../genexus/diagnostics/core/provider/CustomMessageFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java index aae43c5c8..94b2e6138 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java @@ -8,7 +8,7 @@ import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory; -@Plugin(name = "CustomMessageFactory", category = TemplateResolverFactory.CATEGORY) +@Plugin(name = "CustomMessage", category = TemplateResolverFactory.CATEGORY) public final class CustomMessageFactory implements EventResolverFactory { private static final CustomMessageFactory INSTANCE = new CustomMessageFactory(); private CustomMessageFactory() { /* no instances */ } From 89380c4a37fd91287570b621f029548cf07dba61 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Wed, 2 Jul 2025 10:15:01 -0300 Subject: [PATCH 19/24] Build the message only if the loglevel is enabled --- .../diagnostics/core/provider/Log4J2Logger.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 40fdbe771..1961e5f27 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -238,10 +238,12 @@ public void setContext(String key, Object value) { } public void write(String message, int logLevel, Object data, boolean stackTrace) { - if (isJsonLogFormat()) - writeJsonFormat(message, logLevel, data, stackTrace); - else - writeTextFormat(message, logLevel, data, stackTrace); + if (isEnabled(logLevel)) { + if (isJsonLogFormat()) + writeJsonFormat(message, logLevel, data, stackTrace); + else + writeTextFormat(message, logLevel, data, stackTrace); + } } private static final String STACKTRACE_KEY = "stackTrace"; From 49b138debe716f3ed04e0ec2699c896b3b03e4b2 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Wed, 2 Jul 2025 10:37:31 -0300 Subject: [PATCH 20/24] Check if the log format is JSON once. --- .../com/genexus/diagnostics/core/provider/Log4J2Logger.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 1961e5f27..a3624a926 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -22,6 +22,7 @@ public class Log4J2Logger implements ILogger { private org.apache.logging.log4j.Logger log; + private static final boolean IS_JSON_FORMAT = isJsonLogFormat(); public Log4J2Logger(final Class clazz) { log = org.apache.logging.log4j.LogManager.getLogger(clazz); @@ -239,7 +240,7 @@ public void setContext(String key, Object value) { public void write(String message, int logLevel, Object data, boolean stackTrace) { if (isEnabled(logLevel)) { - if (isJsonLogFormat()) + if (IS_JSON_FORMAT) writeJsonFormat(message, logLevel, data, stackTrace); else writeTextFormat(message, logLevel, data, stackTrace); From 70e2f65ca0a03aa66d7f0c6a2f227ecf15a5f5b4 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Wed, 2 Jul 2025 10:39:53 -0300 Subject: [PATCH 21/24] Filters the stack trace to ignore the first lines called from this package --- .../diagnostics/core/provider/Log4J2Logger.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index a3624a926..ef61def01 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -341,7 +341,19 @@ private static boolean isJson(String str) { private static List getStackTraceAsList() { List stackTraceLines = new ArrayList<>(); - for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + + boolean skipping = true; + for (StackTraceElement ste : stackTrace) { + String className = ste.getClassName(); + + // Skip lines from this package + if (skipping && (className.startsWith("com.genexus.diagnostics") || + className.startsWith("java.lang.Thread"))) { + continue; + } + + skipping = false; stackTraceLines.add(ste.toString()); } return stackTraceLines; From 33f00db6b2bb376317d503ef3b957714f8ea06e3 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Wed, 2 Jul 2025 10:47:12 -0300 Subject: [PATCH 22/24] Refactoring --- .../java/com/genexus/diagnostics/Log.java | 23 ++++--- .../java/com/genexus/diagnostics/UserLog.java | 5 +- .../com/genexus/diagnostics/core/ILogger.java | 18 +++--- .../core/provider/Log4J2Logger.java | 61 +++++++++++-------- 4 files changed, 57 insertions(+), 50 deletions(-) diff --git a/common/src/main/java/com/genexus/diagnostics/Log.java b/common/src/main/java/com/genexus/diagnostics/Log.java index a94f15813..28187101f 100644 --- a/common/src/main/java/com/genexus/diagnostics/Log.java +++ b/common/src/main/java/com/genexus/diagnostics/Log.java @@ -7,26 +7,25 @@ public class Log { private static ILogger getLogger() { return getLogger(""); } - + public static ILogger getMainLogger() { return LogManager.getLogger("com.genexus.logging"); } - + private static ILogger getLogger(String topic) { ILogger log; if (topic != null && topic.length() > 0) { log = LogManager.getLogger(topic); - } - else { + } else { log = getMainLogger(); } return log; } - + public static void write(int logLevel, String message, String topic) { write(message, topic, logLevel); } - + public static void write(String message, String topic, int logLevel) { ILogger log = getLogger(topic); LogLevel level = LogLevel.fromInt(logLevel); @@ -51,17 +50,17 @@ public static void write(String message, String topic, int logLevel) { break; default: log.debug(message); - } + } } - + public static void write(String message) { getLogger().debug(message); } - + public static void write(String message, String topic) { getLogger(topic).debug(message); } - + public static void error(String message) { getLogger().error(message); } @@ -85,7 +84,7 @@ public static void fatal(String message, String topic) { public static void fatal(String message, String topic, Throwable ex) { getLogger(topic).fatal(message, ex); } - + public static void warning(String message) { getLogger().warn(message); } @@ -113,7 +112,7 @@ public static void debug(String message) { public static void debug(String message, String topic) { getLogger(topic).debug(message); } - + public static void debug(String message, String topic, Throwable ex) { getLogger(topic).debug(message, ex); } diff --git a/common/src/main/java/com/genexus/diagnostics/UserLog.java b/common/src/main/java/com/genexus/diagnostics/UserLog.java index f306c88ac..dc3f90db3 100644 --- a/common/src/main/java/com/genexus/diagnostics/UserLog.java +++ b/common/src/main/java/com/genexus/diagnostics/UserLog.java @@ -19,10 +19,9 @@ public static ILogger getMainLogger() { private static ILogger getLogger(String topic) { ILogger log; if (topic != null && topic.length() > 0) { - String loggerName = topic.startsWith("$") ? topic.substring(1): String.format("%s.%s", defaultUserLogNamespace, topic.trim()); + String loggerName = topic.startsWith("$") ? topic.substring(1) : String.format("%s.%s", defaultUserLogNamespace, topic.trim()); log = LogManager.getLogger(loggerName); - } - else { + } else { log = getMainLogger(); } return log; diff --git a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java index 403c3ad98..cc4b53a75 100644 --- a/common/src/main/java/com/genexus/diagnostics/core/ILogger.java +++ b/common/src/main/java/com/genexus/diagnostics/core/ILogger.java @@ -1,7 +1,7 @@ package com.genexus.diagnostics.core; public interface ILogger { - + void fatal(String msg, Throwable ex); void fatal(String msg1, String msg2, Throwable ex); @@ -9,9 +9,9 @@ public interface ILogger { void fatal(Throwable ex, String[] list); void fatal(String[] list); - + void fatal(String msg); - + void error(String msg, Throwable ex); void error(String msg1, String msg2, Throwable ex); @@ -19,11 +19,11 @@ public interface ILogger { void error(Throwable ex, String[] list); void error(String[] list); - + void error(String msg); void warn(String msg); - + void warn(Throwable ex, String[] list); void warn(String[] list); @@ -31,7 +31,7 @@ public interface ILogger { void warn(String msg, Throwable ex); void debug(String msg); - + void debug(Throwable ex, String[] list); void debug(String[] list); @@ -41,11 +41,11 @@ public interface ILogger { void debug(String msg, Throwable ex); void info(String[] list); - + void info(String msg); void trace(String msg); - + void trace(Throwable ex, String[] list); void trace(String[] list); @@ -53,7 +53,7 @@ public interface ILogger { void trace(String msg1, String msg2, Throwable ex); void trace(String msg, Throwable ex); - + boolean isDebugEnabled(); boolean isErrorEnabled(); diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index ef61def01..00e7dd4a0 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -4,7 +4,6 @@ import com.genexus.diagnostics.core.ILogger; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.LoggerContext; @@ -21,9 +20,13 @@ import java.util.Map; public class Log4J2Logger implements ILogger { - private org.apache.logging.log4j.Logger log; + private static final String STACKTRACE_KEY = "stackTrace"; + private static final String MESSAGE_KEY = "message"; + private static final String DATA_KEY = "data"; private static final boolean IS_JSON_FORMAT = isJsonLogFormat(); + private final org.apache.logging.log4j.Logger log; + public Log4J2Logger(final Class clazz) { log = org.apache.logging.log4j.LogManager.getLogger(clazz); } @@ -166,7 +169,7 @@ public void warn(String msg) { public void trace(String msg) { log.trace(msg); } - + public void trace(Throwable ex, String[] list) { if (log.isTraceEnabled()) { StringBuilder msg = new StringBuilder(); @@ -209,57 +212,56 @@ public boolean isErrorEnabled() { return log.isErrorEnabled(); } + @Override public boolean isFatalEnabled() { return log.isFatalEnabled(); } + @Override public boolean isWarnEnabled() { return log.isWarnEnabled(); } + @Override public boolean isInfoEnabled() { return log.isInfoEnabled(); } + @Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } + @Override public boolean isEnabled(int logLevel) { return log.isEnabled(getLogLevel(logLevel)); } - public boolean isEnabled(int logLevel, String marker) { - return log.isEnabled(getLogLevel(logLevel), MarkerManager.getMarker(marker)); - } - + @Override public void setContext(String key, Object value) { // Add entry to the MDC (only works for JSON log format) ThreadContext.put(key, fromObjectToString(value)); } + @Override public void write(String message, int logLevel, Object data, boolean stackTrace) { if (isEnabled(logLevel)) { if (IS_JSON_FORMAT) writeJsonFormat(message, logLevel, data, stackTrace); else writeTextFormat(message, logLevel, data, stackTrace); - } + } } - private static final String STACKTRACE_KEY = "stackTrace"; - private static final String MESSAGE_KEY = "message"; - private void writeTextFormat(String message, int logLevel, Object data, boolean stackTrace) { - String dataKey = "data"; Map mapMessage = new LinkedHashMap<>(); if (data == null || (data instanceof String && "null".equals(data.toString()))) { - mapMessage.put(dataKey, JSONObject.NULL); + mapMessage.put(DATA_KEY, JSONObject.NULL); } else if (data instanceof String && isJson((String) data)) { // JSON Strings - mapMessage.put(dataKey, jsonStringToMap((String)data)); + mapMessage.put(DATA_KEY, jsonStringToMap((String) data)); } else { - mapMessage.put(dataKey, data); + mapMessage.put(DATA_KEY, data); } if (stackTrace) { @@ -272,15 +274,14 @@ private void writeTextFormat(String message, int logLevel, Object data, boolean } private void writeJsonFormat(String message, int logLevel, Object data, boolean stackTrace) { - String dataKey = "data"; MapMessage mapMessage = new MapMessage<>().with(MESSAGE_KEY, message); if (data == null || (data instanceof String && "null".equals(data.toString()))) { - mapMessage.with(dataKey, (Object) null); + mapMessage.with(DATA_KEY, (Object) null); } else if (data instanceof String && isJson((String) data)) { // JSON Strings - mapMessage.with(dataKey, jsonStringToMap((String)data)); + mapMessage.with(DATA_KEY, jsonStringToMap((String) data)); } else { - mapMessage.with(dataKey, data); + mapMessage.with(DATA_KEY, data); } if (stackTrace) { @@ -293,13 +294,20 @@ private void writeJsonFormat(String message, int logLevel, Object data, boolean private Level getLogLevel(int logLevel) { LogLevel level = LogLevel.fromInt(logLevel); switch (level) { - case OFF: return Level.OFF; - case TRACE: return Level.TRACE; - case INFO: return Level.INFO; - case WARN: return Level.WARN; - case ERROR: return Level.ERROR; - case FATAL: return Level.FATAL; - default: return Level.DEBUG; + case OFF: + return Level.OFF; + case TRACE: + return Level.TRACE; + case INFO: + return Level.INFO; + case WARN: + return Level.WARN; + case ERROR: + return Level.ERROR; + case FATAL: + return Level.FATAL; + default: + return Level.DEBUG; } } @@ -320,6 +328,7 @@ private static String fromObjectToString(Object value) { res = new JSONArray((List) value).toString(); } else { // Any other object → serialize as JSON + // You never enter here from GX res = JSONObject.quote(value.toString()); } return res; From 15dcce7376165f59147e508c1196b7fbf6c6094e Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Wed, 9 Jul 2025 10:54:39 -0300 Subject: [PATCH 23/24] Maintain insertion order when logging JSONs --- .../core/provider/Log4J2Logger.java | 77 +++++++++++++++++-- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 00e7dd4a0..28f939513 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -2,6 +2,7 @@ import com.genexus.diagnostics.LogLevel; import com.genexus.diagnostics.core.ILogger; +import com.genexus.json.JSONObjectWrapper; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.ThreadContext; @@ -14,10 +15,7 @@ import org.json.JSONArray; import org.json.JSONObject; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class Log4J2Logger implements ILogger { private static final String STACKTRACE_KEY = "stackTrace"; @@ -268,7 +266,7 @@ private void writeTextFormat(String message, int logLevel, Object data, boolean mapMessage.put(STACKTRACE_KEY, getStackTraceAsList()); } - String json = new JSONObject(mapMessage).toString(); + String json = mapToJsonString(mapMessage); String format = "{} - {}"; log.log(getLogLevel(logLevel), format, message, json); } @@ -384,16 +382,23 @@ private static boolean isJsonLogFormat() { } public static Map jsonStringToMap(String jsonString) { - JSONObject jsonObject = new JSONObject(jsonString); + JSONObjectWrapper jsonObject = new JSONObjectWrapper(jsonString); return toMap(jsonObject); } private static Map toMap(JSONObject jsonObject) { Map map = new LinkedHashMap<>(); - for (String key : jsonObject.keySet()) { - Object value = jsonObject.get(key); + + Set> entries = (jsonObject instanceof JSONObjectWrapper) + ? ((JSONObjectWrapper) jsonObject).entrySet() + : jsonObject.toMap().entrySet(); // fallback for other JSONObject + + for (Map.Entry entry : entries) { + String key = entry.getKey(); + Object value = entry.getValue(); map.put(key, convert(value)); } + return map; } @@ -417,4 +422,60 @@ private static Object convert(Object value) { return value; } } + + public static String mapToJsonString(Map map) { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + + sb.append("\"").append(entry.getKey()).append("\":"); + sb.append(toJsonValue(entry.getValue())); + + if (iterator.hasNext()) { + sb.append(","); + } + } + + sb.append("}"); + return sb.toString(); + } + + private static String toJsonValue(Object value) { + if (value == null || value == JSONObject.NULL) { + return "null"; + } else if (value instanceof String) { + return "\"" + value + "\""; + } else if (value instanceof Number || value instanceof Boolean) { + return value.toString(); + } else if (value instanceof JSONObject) { + return mapToJsonString(((JSONObject) value).toMap()); + } else if (value instanceof Map) { + return mapToJsonString((Map) value); + } else if (value instanceof JSONArray) { + return listToJsonString(((JSONArray) value).toList()); + } else if (value instanceof Collection) { + return listToJsonString((Collection) value); + } else { + return "\"" + value.toString() + "\""; // fallback: string + } + } + + private static String listToJsonString(Collection list) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + + Iterator it = list.iterator(); + while (it.hasNext()) { + sb.append(toJsonValue(it.next())); + if (it.hasNext()) { + sb.append(","); + } + } + + sb.append("]"); + return sb.toString(); + } } From 0962556e8edddf9a6152fa34876fa04fe5e96311 Mon Sep 17 00:00:00 2001 From: mrodbratschi Date: Thu, 31 Jul 2025 12:43:35 -0300 Subject: [PATCH 24/24] The string "null" is written as a string and not as a null object (same as in .NET) --- .../com/genexus/diagnostics/core/provider/Log4J2Logger.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java index 28f939513..886ca0911 100644 --- a/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java +++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/Log4J2Logger.java @@ -254,7 +254,7 @@ public void write(String message, int logLevel, Object data, boolean stackTrace) private void writeTextFormat(String message, int logLevel, Object data, boolean stackTrace) { Map mapMessage = new LinkedHashMap<>(); - if (data == null || (data instanceof String && "null".equals(data.toString()))) { + if (data == null) { mapMessage.put(DATA_KEY, JSONObject.NULL); } else if (data instanceof String && isJson((String) data)) { // JSON Strings mapMessage.put(DATA_KEY, jsonStringToMap((String) data)); @@ -274,8 +274,8 @@ private void writeTextFormat(String message, int logLevel, Object data, boolean private void writeJsonFormat(String message, int logLevel, Object data, boolean stackTrace) { MapMessage mapMessage = new MapMessage<>().with(MESSAGE_KEY, message); - if (data == null || (data instanceof String && "null".equals(data.toString()))) { - mapMessage.with(DATA_KEY, (Object) null); + if (data == null) { + mapMessage.with(DATA_KEY, JSONObject.NULL); } else if (data instanceof String && isJson((String) data)) { // JSON Strings mapMessage.with(DATA_KEY, jsonStringToMap((String) data)); } else {