diff --git a/common/src/main/java/com/genexus/diagnostics/Log.java b/common/src/main/java/com/genexus/diagnostics/Log.java
index 4f3476bc0..28187101f 100644
--- a/common/src/main/java/com/genexus/diagnostics/Log.java
+++ b/common/src/main/java/com/genexus/diagnostics/Log.java
@@ -7,63 +7,60 @@ 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);
-
- 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 WARN:
log.warn(message);
break;
- case LogLevel.ERROR:
+ case ERROR:
log.error(message);
break;
- case LogLevel.FATAL:
+ case FATAL:
log.fatal(message);
break;
default:
- log.debug(message);
- }
+ 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);
}
@@ -87,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);
}
@@ -115,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/LogLevel.java b/common/src/main/java/com/genexus/diagnostics/LogLevel.java
index 8279a7835..2b7caf9a1 100644
--- a/common/src/main/java/com/genexus/diagnostics/LogLevel.java
+++ b/common/src/main/java/com/genexus/diagnostics/LogLevel.java
@@ -1,14 +1,24 @@
package com.genexus.diagnostics;
-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 enum LogLevel {
+ OFF(0),
+ TRACE(1),
+ DEBUG(5),
+ INFO(10),
+ WARN(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 210041433..dc3f90db3 100644
--- a/common/src/main/java/com/genexus/diagnostics/UserLog.java
+++ b/common/src/main/java/com/genexus/diagnostics/UserLog.java
@@ -19,37 +19,34 @@ 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;
}
- 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 WARN:
log.warn(message);
break;
- case LogLevel.ERROR:
+ case ERROR:
log.error(message);
break;
- case LogLevel.FATAL:
+ case FATAL:
log.fatal(message);
break;
default:
@@ -120,4 +117,51 @@ public static void debug(String message, String topic) {
public static void debug(String message, String topic, Throwable ex) {
getLogger(topic).debug(message, ex);
}
+
+ 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);
+ }
+
+ public static void write(String message, String topic, int logLevel, Object data) {
+ write(message, topic, 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 79424c5be..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();
@@ -65,5 +65,18 @@ public interface ILogger {
* msg); } }
*/
-
+ default void setContext(String key, Object value) {}
+
+ default void write(String message, int logLevel, Object data, boolean stackTrace) {}
+
+ default boolean isFatalEnabled() { return false; }
+
+ default boolean isWarnEnabled() { return false; }
+
+ default boolean isInfoEnabled() { return false; }
+
+ default boolean isTraceEnabled() { return false; }
+
+ default boolean isEnabled(int logLevel) { return false; }
+
}
diff --git a/wrappercommon/pom.xml b/wrappercommon/pom.xml
index c94606440..ad97e9a3d 100644
--- a/wrappercommon/pom.xml
+++ b/wrappercommon/pom.xml
@@ -33,8 +33,13 @@
org.apache.ws.security
wss4j
1.6.19
-
-
+
+
+ org.apache.logging.log4j
+ log4j-layout-template-json
+ 2.24.3
+
+
gxwrappercommon
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..94b2e6138
--- /dev/null
+++ b/wrappercommon/src/main/java/com/genexus/diagnostics/core/provider/CustomMessageFactory.java
@@ -0,0 +1,30 @@
+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 = "CustomMessage", 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() {
+ 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());
+ }
+}
+
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..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
@@ -1,9 +1,29 @@
package com.genexus.diagnostics.core.provider;
+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;
+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.MapMessage;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.*;
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);
@@ -147,7 +167,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();
@@ -190,4 +210,272 @@ 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));
+ }
+
+ @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 void writeTextFormat(String message, int logLevel, Object data, boolean stackTrace) {
+ Map mapMessage = new LinkedHashMap<>();
+
+ 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));
+ } else {
+ mapMessage.put(DATA_KEY, data);
+ }
+
+ if (stackTrace) {
+ mapMessage.put(STACKTRACE_KEY, getStackTraceAsList());
+ }
+
+ String json = mapToJsonString(mapMessage);
+ String format = "{} - {}";
+ log.log(getLogLevel(logLevel), format, message, json);
+ }
+
+ private void writeJsonFormat(String message, int logLevel, Object data, boolean stackTrace) {
+ MapMessage, ?> mapMessage = new MapMessage<>().with(MESSAGE_KEY, message);
+
+ 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 {
+ mapMessage.with(DATA_KEY, data);
+ }
+
+ if (stackTrace) {
+ mapMessage.with(STACKTRACE_KEY, getStackTraceAsList());
+ }
+
+ log.log(getLogLevel(logLevel), mapMessage);
+ }
+
+ 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;
+ }
+ }
+
+ 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) {
+ res = new JSONObject((Map, ?>) value).toString();
+ } else if (value instanceof List) {
+ 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;
+ }
+
+ private static boolean isJson(String str) {
+ try {
+ new JSONObject(str);
+ return true;
+ } catch (Exception e1) {
+ try {
+ new JSONArray(str);
+ return true;
+ } catch (Exception e2) {
+ return false;
+ }
+ }
+ }
+
+ private static List getStackTraceAsList() {
+ List stackTraceLines = new ArrayList<>();
+ 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;
+ }
+
+ 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 = appender.getLayout();
+ if (layout instanceof JsonTemplateLayout) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static Map jsonStringToMap(String jsonString) {
+ JSONObjectWrapper jsonObject = new JSONObjectWrapper(jsonString);
+ return toMap(jsonObject);
+ }
+
+ private static Map toMap(JSONObject jsonObject) {
+ Map map = new LinkedHashMap<>();
+
+ 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;
+ }
+
+ private static List