From 28d08a12828a54965a8e80c1ec182f80544919a1 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Wed, 19 Nov 2025 18:14:30 +0100 Subject: [PATCH 01/24] Working channelName in the CLI, client UI not updating Signed-off-by: Nico Piel Signed-off-by: Nico Piel --- .../plugins/serverlog/ServerLogPanel.java | 11 ++++---- .../donkey/server/channel/Channel.java | 11 ++++++++ .../server/channel/DestinationChain.java | 15 +++++++++- .../server/channel/DestinationConnector.java | 8 ++++++ .../plugins/serverlog/ArrayAppender.java | 13 ++++++++- .../plugins/serverlog/ServerLogItem.java | 28 +++++++++++++++++-- .../plugins/serverlog/ServerLogProvider.java | 4 +-- 7 files changed, 79 insertions(+), 11 deletions(-) diff --git a/client/src/com/mirth/connect/plugins/serverlog/ServerLogPanel.java b/client/src/com/mirth/connect/plugins/serverlog/ServerLogPanel.java index 1d16428380..c7309fc6cf 100644 --- a/client/src/com/mirth/connect/plugins/serverlog/ServerLogPanel.java +++ b/client/src/com/mirth/connect/plugins/serverlog/ServerLogPanel.java @@ -39,6 +39,7 @@ public class ServerLogPanel extends javax.swing.JPanel { private static final String ID_COLUMN_HEADER = "Id"; + private static final String CHANNEL_COLUMN_HEADER = "Channel"; private static final String LOG_INFO_COLUMN_HEADER = "Log Information"; private JPopupMenu rightclickPopup; private static final int PAUSED = 0; @@ -112,7 +113,7 @@ public void mouseClicked(java.awt.event.MouseEvent evt) { if (evt.getClickCount() >= 2) { // synchronizing this to prevent ArrayIndexOutOfBoundsException since the server log table is constantly being redrawn. synchronized (this) { - new ViewServerLogContentDialog(parent, String.valueOf(logTable.getModel().getValueAt(logTable.convertRowIndexToModel(logTable.getSelectedRow()), 1))); + new ViewServerLogContentDialog(parent, String.valueOf(logTable.getModel().getValueAt(logTable.convertRowIndexToModel(logTable.getSelectedRow()), 2))); } } } @@ -185,13 +186,13 @@ public synchronized void updateTable(LinkedList serverLogs) { for (ServerLogItem item : serverLogs) { if (serverId == null || serverId.equals(item.getServerId())) { - dataList.add(new Object[] { item.getId(), item }); + dataList.add(new Object[] { item.getId(), item.getChannelName(), item }); } } tableData = dataList.toArray(new Object[dataList.size()][]); } else { - tableData = new Object[0][2]; + tableData = new Object[0][3]; } if (logTable != null) { @@ -199,10 +200,10 @@ public synchronized void updateTable(LinkedList serverLogs) { model.refreshDataVector(tableData); } else { logTable = new MirthTable(); - logTable.setModel(new RefreshTableModel(tableData, new String[] { ID_COLUMN_HEADER, + logTable.setModel(new RefreshTableModel(tableData, new String[] { ID_COLUMN_HEADER, CHANNEL_COLUMN_HEADER, LOG_INFO_COLUMN_HEADER }) { - boolean[] canEdit = new boolean[] { false, false }; + boolean[] canEdit = new boolean[] { false, false, false }; public boolean isCellEditable(int rowIndex, int columnIndex) { return canEdit[columnIndex]; diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java index a78f2371f7..9290a8a573 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java @@ -40,6 +40,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; import com.mirth.connect.donkey.model.DonkeyException; import com.mirth.connect.donkey.model.channel.DebugOptions; @@ -1271,6 +1272,9 @@ protected DispatchResult dispatchRawMessage(RawMessage rawMessage, boolean batch } else { currentThread.setName("Channel Dispatch Thread on " + name + " (" + channelId + ") < " + originalThreadName); } + + ThreadContext.put("channelId", channelId); + ThreadContext.put("channelName", name); DonkeyDao dao = null; boolean commitSuccess = false; @@ -1386,6 +1390,8 @@ protected DispatchResult dispatchRawMessage(RawMessage rawMessage, boolean batch dispatchThreads.remove(currentThread); } currentThread.setName(originalThreadName); + ThreadContext.remove("channelId"); + ThreadContext.remove("channelName"); } } @@ -1934,11 +1940,16 @@ public void processUnfinishedMessages() throws Exception { @Override public void run() { try { + ThreadContext.put("channelId", channelId); + ThreadContext.put("channelName", name); do { processSourceQueue(Constants.SOURCE_QUEUE_POLL_TIMEOUT_MILLIS); } while (isActive() && !stopSourceQueue); } catch (InterruptedException e) { Thread.currentThread().interrupt(); + } finally { + ThreadContext.remove("channelId"); + ThreadContext.remove("channelName"); } } diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java index 3a09743f36..1c72213b77 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java @@ -18,6 +18,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; import com.mirth.connect.donkey.model.message.ConnectorMessage; import com.mirth.connect.donkey.model.message.ContentType; @@ -61,8 +62,20 @@ public List call() throws InterruptedException { String originalThreadName = Thread.currentThread().getName(); try { Thread.currentThread().setName(name + " < " + originalThreadName); + String channelId = chainProvider.getChannelId(); + String channelName = null; + if (!chainProvider.getDestinationConnectors().isEmpty()) { + channelName = chainProvider.getDestinationConnectors().values().iterator().next().getChannel().getName(); + } + + ThreadContext.put("channelId", channelId); + if (channelName != null) { + ThreadContext.put("channelName", channelName); + } return doCall(); } finally { + ThreadContext.remove("channelId"); + ThreadContext.remove("channelName"); Thread.currentThread().setName(originalThreadName); } } @@ -210,4 +223,4 @@ private List doCall() throws InterruptedException { return messages; } -} \ No newline at end of file +} diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java index dd74761264..e70f9f65d6 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java @@ -24,6 +24,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; import com.mirth.connect.donkey.model.DonkeyException; import com.mirth.connect.donkey.model.channel.ConnectorProperties; @@ -613,6 +614,10 @@ public void processPendingConnectorMessage(DonkeyDao dao, ConnectorMessage messa @Override public void run() { + // Add channel info to ThreadContext + ThreadContext.put("channelId", getChannelId()); + ThreadContext.put("channelName", channel.getName()); + DonkeyDao dao = null; boolean commitSuccess = false; Serializer serializer = channel.getSerializer(); @@ -892,6 +897,9 @@ public void run() { } } } while ((getCurrentState() == DeployedState.STARTED || getCurrentState() == DeployedState.STARTING) && !stopQueue.get()); + + ThreadContext.remove("channelId"); + ThreadContext.remove("channelName"); } private Response handleSend(ConnectorProperties connectorProperties, ConnectorMessage message) throws InterruptedException { diff --git a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java index c53f248fca..6d80413289 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java @@ -36,6 +36,17 @@ public void append(LogEvent logEvent) { return; } + String channelId = null; + String channelName = null; + if (logEvent.getContextMap() != null) { + if (logEvent.getContextMap().containsKey("channelId")) { + channelId = logEvent.getContextMap().get("channelId"); + } + if (logEvent.getContextMap().containsKey("channelName")) { + channelName = logEvent.getContextMap().get("channelName"); + } + } + String level = String.valueOf(logEvent.getLevel()); Date date = new Date(logEvent.getTimeMillis()); String threadName = logEvent.getThreadName(); @@ -61,6 +72,6 @@ public void append(LogEvent logEvent) { throwableInformation = logText.toString(); } - serverLogProvider.newServerLogReceived(level, date, threadName, category, lineNumber, message, throwableInformation); + serverLogProvider.newServerLogReceived(channelId, channelName, level, date, threadName, category, lineNumber, message, throwableInformation); } } diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java index a88bdf3f9a..5158a19f6d 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java @@ -21,6 +21,7 @@ public class ServerLogItem implements Serializable { private String serverId; private Long id; + private String channelId; private String level; private Date date; private String threadName; @@ -28,16 +29,23 @@ public class ServerLogItem implements Serializable { private String lineNumber; private String message; private String throwableInformation; + private String channelName; public ServerLogItem() {} public ServerLogItem(String message) { - this(null, null, null, null, null, null, null, message, null); + this(null, null, null, null, null, null, null, null, null, message, null); } - + public ServerLogItem(String serverId, Long id, String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation) { + this(serverId, id, null, null, level, date, threadName, category, lineNumber, message, throwableInformation); + } + + public ServerLogItem(String serverId, Long id, String channelId, String channelName, String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation) { this.serverId = serverId; this.id = id; + this.channelId = channelId; + this.channelName = channelName; this.level = level; this.date = date; this.threadName = threadName; @@ -62,6 +70,14 @@ public Long getId() { public void setId(Long id) { this.id = id; } + + public String getChannelId() { + return channelId; + } + + public void setChannelId(String channelId) { + this.channelId = channelId; + } public String getLevel() { return level; @@ -118,6 +134,14 @@ public String getThrowableInformation() { public void setThrowableInformation(String throwableInformation) { this.throwableInformation = throwableInformation; } + + public String getChannelName() { + return channelName; + } + + public void setChannelName(String channelName) { + this.channelName = channelName; + } @Override public String toString() { diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java index f4aca737c6..baa7956d2c 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java @@ -55,9 +55,9 @@ public void init(Properties properties) { initialize(); } - public synchronized void newServerLogReceived(String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation) { + public synchronized void newServerLogReceived(String channelId, String channelName, String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation) { if (logController != null) { - logController.addLogItem(new ServerLogItem(serverId, logId, level, date, threadName, category, lineNumber, message, throwableInformation)); + logController.addLogItem(new ServerLogItem(serverId, logId, channelId, channelName, level, date, threadName, category, lineNumber, message, throwableInformation)); logId++; } } From 34dbab0957fee8f0ae1dd6e956c9d1297d124f70 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Wed, 19 Nov 2025 18:42:38 +0100 Subject: [PATCH 02/24] Channel Columns in the UI works Signed-off-by: Nico Piel Signed-off-by: Nico Piel --- .../util/javascript/JavaScriptTask.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java b/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java index dd3404b828..9f616fa88a 100644 --- a/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java +++ b/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java @@ -14,6 +14,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; import org.mozilla.javascript.Context; import org.mozilla.javascript.Script; import org.mozilla.javascript.Scriptable; @@ -28,6 +29,8 @@ public abstract class JavaScriptTask implements Callable { private Logger logger = LogManager.getLogger(JavaScriptTask.class); private MirthContextFactory contextFactory; private String threadName; + private String channelId; + private String channelName; private Context context; private boolean contextCreated = false; @@ -74,6 +77,9 @@ private JavaScriptTask(MirthContextFactory contextFactory) { } private void init(String name, String channelId, String channelName, Integer metaDataId, String destinationName) { + this.channelId = channelId; + this.channelName = channelName; + StringBuilder builder = new StringBuilder(name).append(" JavaScript Task"); if (StringUtils.isNotEmpty(channelName)) { builder.append(" on ").append(channelName); @@ -111,8 +117,22 @@ public final T call() throws Exception { String originalThreadName = Thread.currentThread().getName(); try { Thread.currentThread().setName(threadName + " < " + originalThreadName); + + if (channelId != null) { + ThreadContext.put("channelId", channelId); + } + if (channelName != null) { + ThreadContext.put("channelName", channelName); + } + return doCall(); } finally { + if (channelName != null) { + ThreadContext.remove("channelName"); + } + if (channelId != null) { + ThreadContext.remove("channelId"); + } Thread.currentThread().setName(originalThreadName); } } From 1260098ee5790429ace4185784905500f9e78f63 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Wed, 19 Nov 2025 22:58:53 +0100 Subject: [PATCH 03/24] Line breaks Signed-off-by: Nico Piel Signed-off-by: Nico Piel --- .../mirth/connect/plugins/serverlog/ServerLogPanel.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/com/mirth/connect/plugins/serverlog/ServerLogPanel.java b/client/src/com/mirth/connect/plugins/serverlog/ServerLogPanel.java index c7309fc6cf..2f3b73f160 100644 --- a/client/src/com/mirth/connect/plugins/serverlog/ServerLogPanel.java +++ b/client/src/com/mirth/connect/plugins/serverlog/ServerLogPanel.java @@ -200,9 +200,11 @@ public synchronized void updateTable(LinkedList serverLogs) { model.refreshDataVector(tableData); } else { logTable = new MirthTable(); - logTable.setModel(new RefreshTableModel(tableData, new String[] { ID_COLUMN_HEADER, CHANNEL_COLUMN_HEADER, - LOG_INFO_COLUMN_HEADER }) { - + logTable.setModel(new RefreshTableModel(tableData, new String[] { + ID_COLUMN_HEADER, + CHANNEL_COLUMN_HEADER, + LOG_INFO_COLUMN_HEADER + }) { boolean[] canEdit = new boolean[] { false, false, false }; public boolean isCellEditable(int rowIndex, int columnIndex) { From b42783fe87957acfd2034c01ff4368b096f79750 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Wed, 19 Nov 2025 22:59:25 +0100 Subject: [PATCH 04/24] getOrDefault Signed-off-by: Nico Piel Signed-off-by: Nico Piel --- .../connect/plugins/serverlog/ArrayAppender.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java index 6d80413289..586ae2892c 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java @@ -38,13 +38,12 @@ public void append(LogEvent logEvent) { String channelId = null; String channelName = null; + + // Check theoretically not necessary, as getContextMap never returns null + // Safety first if (logEvent.getContextMap() != null) { - if (logEvent.getContextMap().containsKey("channelId")) { - channelId = logEvent.getContextMap().get("channelId"); - } - if (logEvent.getContextMap().containsKey("channelName")) { - channelName = logEvent.getContextMap().get("channelName"); - } + channelId = logEvent.getContextMap().getOrDefault("channelId", ""); + channelName = logEvent.getContextMap().getOrDefault("channelName", ""); } String level = String.valueOf(logEvent.getLevel()); From bcee58da7ec78e82ac9610cecca0dc4e31098ef6 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Wed, 19 Nov 2025 23:00:04 +0100 Subject: [PATCH 05/24] Implemented Builder Pattern Signed-off-by: Nico Piel Signed-off-by: Nico Piel --- .../plugins/serverlog/ServerLogItem.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java index 5158a19f6d..ea50c910a4 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java @@ -55,6 +55,93 @@ public ServerLogItem(String serverId, Long id, String channelId, String channelN this.throwableInformation = throwableInformation; } + private ServerLogItem(Builder builder) { + this.serverId = builder.serverId; + this.id = builder.id; + this.channelId = builder.channelId; + this.channelName = builder.channelName; + this.level = builder.level; + this.date = builder.date; + this.threadName = builder.threadName; + this.category = builder.category; + this.lineNumber = builder.lineNumber; + this.message = builder.message; + this.throwableInformation = builder.throwableInformation; + } + + public static class Builder { + private String serverId; + private Long id; + private String channelId; + private String channelName; + private String level; + private Date date; + private String threadName; + private String category; + private String lineNumber; + private String message; + private String throwableInformation; + + public Builder serverId(String serverId) { + this.serverId = serverId; + return this; + } + + public Builder id(Long id) { + this.id = id; + return this; + } + + public Builder channelId(String channelId) { + this.channelId = channelId; + return this; + } + + public Builder channelName(String channelName) { + this.channelName = channelName; + return this; + } + + public Builder level(String level) { + this.level = level; + return this; + } + + public Builder date(Date date) { + this.date = date; + return this; + } + + public Builder threadName(String threadName) { + this.threadName = threadName; + return this; + } + + public Builder category(String category) { + this.category = category; + return this; + } + + public Builder lineNumber(String lineNumber) { + this.lineNumber = lineNumber; + return this; + } + + public Builder message(String message) { + this.message = message; + return this; + } + + public Builder throwableInformation(String throwableInformation) { + this.throwableInformation = throwableInformation; + return this; + } + + public ServerLogItem build() { + return new ServerLogItem(this); + } + } + public String getServerId() { return serverId; } From 2b555d7759f41b5d0f51fe8285b3135bf5887327 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Wed, 19 Nov 2025 23:18:16 +0100 Subject: [PATCH 06/24] Use CloseableThreadContext Signed-off-by: Nico Piel --- .../donkey/server/channel/DestinationConnector.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java index e70f9f65d6..a28e6aba56 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java @@ -22,6 +22,7 @@ import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.logging.log4j.CloseableThreadContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; @@ -615,10 +616,8 @@ public void processPendingConnectorMessage(DonkeyDao dao, ConnectorMessage messa @Override public void run() { // Add channel info to ThreadContext - ThreadContext.put("channelId", getChannelId()); - ThreadContext.put("channelName", channel.getName()); - - DonkeyDao dao = null; + try (CloseableThreadContext.Instance ctc = CloseableThreadContext.put("channelId", getChannelId()).put("channelName", channel.getName())) { + DonkeyDao dao = null; boolean commitSuccess = false; Serializer serializer = channel.getSerializer(); ConnectorMessage connectorMessage = null; @@ -897,9 +896,7 @@ public void run() { } } } while ((getCurrentState() == DeployedState.STARTED || getCurrentState() == DeployedState.STARTING) && !stopQueue.get()); - - ThreadContext.remove("channelId"); - ThreadContext.remove("channelName"); + } } private Response handleSend(ConnectorProperties connectorProperties, ConnectorMessage message) throws InterruptedException { From 2d118e48355ad38dd085dee748aaf71e8ca6d770 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Wed, 19 Nov 2025 23:22:42 +0100 Subject: [PATCH 07/24] Have to add this line to include the new Builder pattern internal class Signed-off-by: Nico Piel --- server/build.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/server/build.xml b/server/build.xml index 1e66e78173..92bfa030b8 100644 --- a/server/build.xml +++ b/server/build.xml @@ -888,6 +888,7 @@ + From 88c452b318f9e78e882a42dff0da5fdafc8b8e3f Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Thu, 20 Nov 2025 13:23:58 +0100 Subject: [PATCH 08/24] Implemented a HashMap instead Signed-off-by: Nico Piel --- .../plugins/serverlog/ServerLogClient.java | 8 +- .../plugins/serverlog/ServerLogItem.java | 132 ++++-------------- .../plugins/serverlog/ServerLogProvider.java | 17 ++- .../servlets/SwaggerExamplesServlet.java | 16 ++- 4 files changed, 61 insertions(+), 112 deletions(-) diff --git a/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java b/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java index 36e93ca315..c4e0d63bbc 100644 --- a/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java +++ b/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java @@ -9,9 +9,7 @@ package com.mirth.connect.plugins.serverlog; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import javax.swing.JComponent; @@ -26,7 +24,9 @@ public class ServerLogClient extends DashboardTabPlugin { private ServerLogPanel serverLogPanel; private LinkedList serverLogs; - private static final ServerLogItem unauthorizedLog = new ServerLogItem("You are not authorized to view the server log."); + private static final ServerLogItem unauthorizedLog = new ServerLogItem(new HashMap(){{ + put("message", "You are not authorized to view the server log."); + }}); private int currentServerLogSize; private boolean receivedNewLogs; private Long lastLogId; diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java index ea50c910a4..83b42a0c0d 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java @@ -12,6 +12,8 @@ import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -30,116 +32,28 @@ public class ServerLogItem implements Serializable { private String message; private String throwableInformation; private String channelName; + private HashMap context; public ServerLogItem() {} - public ServerLogItem(String message) { - this(null, null, null, null, null, null, null, null, null, message, null); - } - - public ServerLogItem(String serverId, Long id, String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation) { - this(serverId, id, null, null, level, date, threadName, category, lineNumber, message, throwableInformation); - } - - public ServerLogItem(String serverId, Long id, String channelId, String channelName, String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation) { - this.serverId = serverId; - this.id = id; - this.channelId = channelId; - this.channelName = channelName; - this.level = level; - this.date = date; - this.threadName = threadName; - this.category = category; - this.lineNumber = lineNumber; - this.message = message; - this.throwableInformation = throwableInformation; - } - - private ServerLogItem(Builder builder) { - this.serverId = builder.serverId; - this.id = builder.id; - this.channelId = builder.channelId; - this.channelName = builder.channelName; - this.level = builder.level; - this.date = builder.date; - this.threadName = builder.threadName; - this.category = builder.category; - this.lineNumber = builder.lineNumber; - this.message = builder.message; - this.throwableInformation = builder.throwableInformation; - } - - public static class Builder { - private String serverId; - private Long id; - private String channelId; - private String channelName; - private String level; - private Date date; - private String threadName; - private String category; - private String lineNumber; - private String message; - private String throwableInformation; - - public Builder serverId(String serverId) { - this.serverId = serverId; - return this; - } - - public Builder id(Long id) { - this.id = id; - return this; - } - - public Builder channelId(String channelId) { - this.channelId = channelId; - return this; - } - - public Builder channelName(String channelName) { - this.channelName = channelName; - return this; - } - - public Builder level(String level) { - this.level = level; - return this; - } - - public Builder date(Date date) { - this.date = date; - return this; - } - - public Builder threadName(String threadName) { - this.threadName = threadName; - return this; - } - - public Builder category(String category) { - this.category = category; - return this; - } - - public Builder lineNumber(String lineNumber) { - this.lineNumber = lineNumber; - return this; - } - - public Builder message(String message) { - this.message = message; - return this; - } - - public Builder throwableInformation(String throwableInformation) { - this.throwableInformation = throwableInformation; - return this; + public ServerLogItem(Map properties) { + if (properties != null) { + this.context = new HashMap<>(properties); + } else { + this.context = new HashMap<>(); } - public ServerLogItem build() { - return new ServerLogItem(this); - } + this.serverId = (String) context.getOrDefault("serverId", ""); + this.id = (Long) context.getOrDefault("id", ""); + this.channelId = (String) context.getOrDefault("channelId", ""); + this.channelName = (String) context.getOrDefault("channelName", ""); + this.level = (String) context.getOrDefault("level", ""); + this.date = (Date) context.getOrDefault("date", ""); + this.threadName = (String) context.getOrDefault("threadName", ""); + this.category = (String) context.getOrDefault("category", ""); + this.lineNumber = (String) context.getOrDefault("lineNumber", ""); + this.message = (String) context.getOrDefault("message", ""); + this.throwableInformation = (String) context.getOrDefault("throwableInformation", ""); } public String getServerId() { @@ -229,6 +143,14 @@ public String getChannelName() { public void setChannelName(String channelName) { this.channelName = channelName; } + + public Map getContext() { + return context; + } + + public void setContext(Map context) { + this.context = new HashMap<>(context); + } @Override public String toString() { diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java index baa7956d2c..eae755ca39 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java @@ -14,7 +14,9 @@ import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import org.apache.logging.log4j.LogManager; @@ -57,7 +59,20 @@ public void init(Properties properties) { public synchronized void newServerLogReceived(String channelId, String channelName, String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation) { if (logController != null) { - logController.addLogItem(new ServerLogItem(serverId, logId, channelId, channelName, level, date, threadName, category, lineNumber, message, throwableInformation)); + Map properties = new HashMap<>(); + properties.put("serverId", serverId); + properties.put("id", logId); + properties.put("channelId", channelId); + properties.put("channelName", channelName); + properties.put("level", level); + properties.put("date", date); + properties.put("threadName", threadName); + properties.put("category", category); + properties.put("lineNumber", lineNumber); + properties.put("message", message); + properties.put("throwableInformation", throwableInformation); + + logController.addLogItem(new ServerLogItem(properties)); logId++; } } diff --git a/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java b/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java index 476775c82b..e8e9b9af1d 100644 --- a/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java +++ b/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java @@ -1286,8 +1286,20 @@ private List getServerEventListExample() { } private ServerLogItem getServerLogItemExample() { - return new ServerLogItem(UUID.randomUUID().toString(), 1L, "INFO", dateNow.getTime(), "Main Server Thread", "com.mirth.connect.server.Mirth", "1", "Example message", "Example throwable information"); - } + // return new ServerLogItem(UUID.randomUUID().toString(), 1L, "INFO", dateNow.getTime(), "Main Server Thread", "com.mirth.connect.server.Mirth", "1", "Example message", "Example throwable information"); + Map properties = new HashMap<>(); + properties.put("serverId", UUID.randomUUID().toString()); + properties.put("id", 1L); + properties.put("channelId", "INFO"); + properties.put("date", dateNow.getTime()); + properties.put("threadName", "Main Server Thread"); + properties.put("category", "com.mirth.connect.server.Mirth"); + properties.put("lineNumber", "1"); + properties.put("message", "Example Message"); + properties.put("throwableInformation", "Example throwable Information"); + + return new ServerLogItem(properties); + } private List getServerLogItemListExample() { List serverLogList = new ArrayList<>(); From 790a60e96c48459ac0769811a786b8d9be710e5e Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Thu, 20 Nov 2025 14:42:29 +0100 Subject: [PATCH 09/24] CloseableThreadContext where applicable Signed-off-by: Nico Piel --- .../donkey/server/channel/Channel.java | 21 +- .../server/channel/DestinationChain.java | 13 +- .../server/channel/DestinationConnector.java | 472 +++++++++--------- .../util/javascript/JavaScriptTask.java | 19 +- 4 files changed, 257 insertions(+), 268 deletions(-) diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java index 9290a8a573..4ad283e926 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java @@ -38,6 +38,7 @@ import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.logging.log4j.CloseableThreadContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; @@ -1258,7 +1259,10 @@ protected DispatchResult dispatchRawMessage(RawMessage rawMessage, boolean batch boolean lockAcquired = false; Long persistedMessageId = null; - try { + try (CloseableThreadContext.Instance ctc = CloseableThreadContext + .put("channelId", channelId) + .put("channelName", name) + ) { synchronized (dispatchThreads) { if (!shuttingDown) { dispatchThreads.add(currentThread); @@ -1272,9 +1276,6 @@ protected DispatchResult dispatchRawMessage(RawMessage rawMessage, boolean batch } else { currentThread.setName("Channel Dispatch Thread on " + name + " (" + channelId + ") < " + originalThreadName); } - - ThreadContext.put("channelId", channelId); - ThreadContext.put("channelName", name); DonkeyDao dao = null; boolean commitSuccess = false; @@ -1390,8 +1391,6 @@ protected DispatchResult dispatchRawMessage(RawMessage rawMessage, boolean batch dispatchThreads.remove(currentThread); } currentThread.setName(originalThreadName); - ThreadContext.remove("channelId"); - ThreadContext.remove("channelName"); } } @@ -1939,17 +1938,15 @@ public void processUnfinishedMessages() throws Exception { @Override public void run() { - try { - ThreadContext.put("channelId", channelId); - ThreadContext.put("channelName", name); + try (CloseableThreadContext.Instance ctc = CloseableThreadContext + .put("channelId", channelId) + .put("channelName", name) + ) { do { processSourceQueue(Constants.SOURCE_QUEUE_POLL_TIMEOUT_MILLIS); } while (isActive() && !stopSourceQueue); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - } finally { - ThreadContext.remove("channelId"); - ThreadContext.remove("channelName"); } } diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java index 1c72213b77..c48b6dec74 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java @@ -16,6 +16,7 @@ import java.util.concurrent.Callable; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.CloseableThreadContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; @@ -64,18 +65,18 @@ public List call() throws InterruptedException { Thread.currentThread().setName(name + " < " + originalThreadName); String channelId = chainProvider.getChannelId(); String channelName = null; + if (!chainProvider.getDestinationConnectors().isEmpty()) { channelName = chainProvider.getDestinationConnectors().values().iterator().next().getChannel().getName(); } - ThreadContext.put("channelId", channelId); - if (channelName != null) { - ThreadContext.put("channelName", channelName); + try (CloseableThreadContext.Instance ctc = CloseableThreadContext + .put("channelId", channelId) + .put("channelName", channelName != null ? channelName : "") + ) { + return doCall(); } - return doCall(); } finally { - ThreadContext.remove("channelId"); - ThreadContext.remove("channelName"); Thread.currentThread().setName(originalThreadName); } } diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java index a28e6aba56..dfcf3125b3 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java @@ -618,284 +618,284 @@ public void run() { // Add channel info to ThreadContext try (CloseableThreadContext.Instance ctc = CloseableThreadContext.put("channelId", getChannelId()).put("channelName", channel.getName())) { DonkeyDao dao = null; - boolean commitSuccess = false; - Serializer serializer = channel.getSerializer(); - ConnectorMessage connectorMessage = null; - int retryIntervalMillis = destinationConnectorProperties.getRetryIntervalMillis(); - AtomicBoolean waitingRetryInterval = ((DestinationQueueThread) Thread.currentThread()).getWaitingRetryInterval(); - Long lastMessageId = null; - boolean canAcquire = true; - Lock statusUpdateLock = null; - queue.registerThreadId(); - - do { - try { - if (canAcquire) { - connectorMessage = queue.acquire(); - } + boolean commitSuccess = false; + Serializer serializer = channel.getSerializer(); + ConnectorMessage connectorMessage = null; + int retryIntervalMillis = destinationConnectorProperties.getRetryIntervalMillis(); + AtomicBoolean waitingRetryInterval = ((DestinationQueueThread) Thread.currentThread()).getWaitingRetryInterval(); + Long lastMessageId = null; + boolean canAcquire = true; + Lock statusUpdateLock = null; + queue.registerThreadId(); - if (connectorMessage != null) { - boolean exceptionCaught = false; + do { + try { + if (canAcquire) { + connectorMessage = queue.acquire(); + } - try { - /* - * If the last message id is equal to the current message id, then the - * message was not successfully sent and is being retried, so wait the retry - * interval. - * - * If the last message id is greater than the current message id, then some - * message was not successful, message rotation is on, and the queue is back - * to the oldest message, so wait the retry interval. - */ - if (connectorMessage.isAttemptedFirst() || lastMessageId != null && (lastMessageId == connectorMessage.getMessageId() || (queue.isRotate() && lastMessageId > connectorMessage.getMessageId() && queue.hasBeenRotated()))) { - try { - waitingRetryInterval.set(true); - Thread.sleep(retryIntervalMillis); - } finally { - synchronized (waitingRetryInterval) { - waitingRetryInterval.set(false); + if (connectorMessage != null) { + boolean exceptionCaught = false; + + try { + /* + * If the last message id is equal to the current message id, then the + * message was not successfully sent and is being retried, so wait the retry + * interval. + * + * If the last message id is greater than the current message id, then some + * message was not successful, message rotation is on, and the queue is back + * to the oldest message, so wait the retry interval. + */ + if (connectorMessage.isAttemptedFirst() || lastMessageId != null && (lastMessageId == connectorMessage.getMessageId() || (queue.isRotate() && lastMessageId > connectorMessage.getMessageId() && queue.hasBeenRotated()))) { + try { + waitingRetryInterval.set(true); + Thread.sleep(retryIntervalMillis); + } finally { + synchronized (waitingRetryInterval) { + waitingRetryInterval.set(false); + } } + + connectorMessage.setAttemptedFirst(false); } - connectorMessage.setAttemptedFirst(false); - } + lastMessageId = connectorMessage.getMessageId(); - lastMessageId = connectorMessage.getMessageId(); + dao = daoFactory.getDao(); + Status previousStatus = connectorMessage.getStatus(); - dao = daoFactory.getDao(); - Status previousStatus = connectorMessage.getStatus(); + Class connectorPropertiesClass = getConnectorProperties().getClass(); + Class serializedPropertiesClass = null; - Class connectorPropertiesClass = getConnectorProperties().getClass(); - Class serializedPropertiesClass = null; + ConnectorProperties connectorProperties = null; - ConnectorProperties connectorProperties = null; + /* + * If we're not regenerating connector properties, use the serialized sent + * content from the database. It's possible that the channel had Regenerate + * Template and Include Filter/Transformer enabled at one point, and then + * was disabled later, so we also have to make sure the sent content exists. + */ + if (!destinationConnectorProperties.isRegenerateTemplate() && connectorMessage.getSent() != null) { + // Attempt to get the sent properties from the in-memory cache. If it doesn't exist, deserialize from the actual sent content. + connectorProperties = connectorMessage.getSentProperties(); + if (connectorProperties == null) { + connectorProperties = serializer.deserialize(connectorMessage.getSent().getContent(), ConnectorProperties.class); + connectorMessage.setSentProperties(connectorProperties); + } - /* - * If we're not regenerating connector properties, use the serialized sent - * content from the database. It's possible that the channel had Regenerate - * Template and Include Filter/Transformer enabled at one point, and then - * was disabled later, so we also have to make sure the sent content exists. - */ - if (!destinationConnectorProperties.isRegenerateTemplate() && connectorMessage.getSent() != null) { - // Attempt to get the sent properties from the in-memory cache. If it doesn't exist, deserialize from the actual sent content. - connectorProperties = connectorMessage.getSentProperties(); - if (connectorProperties == null) { - connectorProperties = serializer.deserialize(connectorMessage.getSent().getContent(), ConnectorProperties.class); - connectorMessage.setSentProperties(connectorProperties); + serializedPropertiesClass = connectorProperties.getClass(); + } else { + connectorProperties = ((DestinationConnectorPropertiesInterface) getConnectorProperties()).clone(); } - serializedPropertiesClass = connectorProperties.getClass(); - } else { - connectorProperties = ((DestinationConnectorPropertiesInterface) getConnectorProperties()).clone(); - } - - /* - * Verify that the connector properties stored in the connector message - * match the properties from the current connector. Otherwise the connector - * type has changed and the message will be set to errored. If we're - * regenerating the connector properties then it doesn't matter. - */ - if (connectorMessage.getSent() == null || destinationConnectorProperties.isRegenerateTemplate() || serializedPropertiesClass == connectorPropertiesClass) { - ThreadUtils.checkInterruptedStatus(); - /* - * If a historical queued message has not yet been transformed and the - * current queue settings do not include the filter/transformer, force - * the message to ERROR. + * Verify that the connector properties stored in the connector message + * match the properties from the current connector. Otherwise the connector + * type has changed and the message will be set to errored. If we're + * regenerating the connector properties then it doesn't matter. */ - if (connectorMessage.getSent() == null && !includeFilterTransformerInQueue()) { + if (connectorMessage.getSent() == null || destinationConnectorProperties.isRegenerateTemplate() || serializedPropertiesClass == connectorPropertiesClass) { + ThreadUtils.checkInterruptedStatus(); + + /* + * If a historical queued message has not yet been transformed and the + * current queue settings do not include the filter/transformer, force + * the message to ERROR. + */ + if (connectorMessage.getSent() == null && !includeFilterTransformerInQueue()) { + connectorMessage.setStatus(Status.ERROR); + connectorMessage.setProcessingError("Queued message has not yet been transformed, and Include Filter/Transformer is currently disabled."); + + dao.updateStatus(connectorMessage, previousStatus); + dao.updateErrors(connectorMessage); + } else { + if (includeFilterTransformerInQueue()) { + transform(dao, connectorMessage, previousStatus, connectorMessage.getSent() == null); + } + + if (connectorMessage.getStatus() == Status.QUEUED) { + /* + * Replace the connector properties if necessary. Again for + * historical queue reasons, we need to check whether the sent + * content exists. + */ + if (connectorMessage.getSent() == null || destinationConnectorProperties.isRegenerateTemplate()) { + replaceConnectorProperties(connectorProperties, connectorMessage); + MessageContent sentContent = getSentContent(connectorMessage, connectorProperties); + connectorMessage.setSent(sentContent); + + if (sentContent != null && storageSettings.isStoreSent()) { + ThreadUtils.checkInterruptedStatus(); + dao.storeMessageContent(sentContent); + } + } + + Response response = handleSend(connectorProperties, connectorMessage); + connectorMessage.setSendAttempts(connectorMessage.getSendAttempts() + 1); + + if (response == null) { + throw new RuntimeException("Received null response from destination " + destinationName + "."); + } + response.fixStatus(isQueueEnabled()); + + afterSend(dao, connectorMessage, response, previousStatus); + } + } + } else { connectorMessage.setStatus(Status.ERROR); - connectorMessage.setProcessingError("Queued message has not yet been transformed, and Include Filter/Transformer is currently disabled."); + connectorMessage.setProcessingError("Mismatched connector properties detected in queued message. The connector type may have changed since the message was queued.\nFOUND: " + serializedPropertiesClass.getSimpleName() + "\nEXPECTED: " + connectorPropertiesClass.getSimpleName()); dao.updateStatus(connectorMessage, previousStatus); dao.updateErrors(connectorMessage); - } else { - if (includeFilterTransformerInQueue()) { - transform(dao, connectorMessage, previousStatus, connectorMessage.getSent() == null); - } + } - if (connectorMessage.getStatus() == Status.QUEUED) { + /* + * If we're about to commit a non-QUEUED status, we first need to obtain a + * read lock from the queue. This is done so that if something else + * invalidates the queue at the same time, we don't incorrectly decrement + * the size during the release. + */ + if (connectorMessage.getStatus() != Status.QUEUED) { + Lock lock = queue.getStatusUpdateLock(); + lock.lock(); + statusUpdateLock = lock; + } + + ThreadUtils.checkInterruptedStatus(); + dao.commit(storageSettings.isDurable()); + commitSuccess = true; + + // Only actually attempt to remove content if the status is SENT + if (connectorMessage.getStatus().isCompleted()) { + try { + channel.removeContent(dao, null, lastMessageId, true, true); + } catch (RuntimeException e) { /* - * Replace the connector properties if necessary. Again for - * historical queue reasons, we need to check whether the sent - * content exists. + * The connector message itself processed successfully, only the + * remove content operation failed. In this case just give up and + * log an error. */ - if (connectorMessage.getSent() == null || destinationConnectorProperties.isRegenerateTemplate()) { - replaceConnectorProperties(connectorProperties, connectorMessage); - MessageContent sentContent = getSentContent(connectorMessage, connectorProperties); - connectorMessage.setSent(sentContent); - - if (sentContent != null && storageSettings.isStoreSent()) { - ThreadUtils.checkInterruptedStatus(); - dao.storeMessageContent(sentContent); - } - } - - Response response = handleSend(connectorProperties, connectorMessage); - connectorMessage.setSendAttempts(connectorMessage.getSendAttempts() + 1); + logger.error("Error removing content for message " + lastMessageId + " for channel " + channel.getName() + " (" + channel.getChannelId() + ") on destination " + destinationName + ". This error is expected if the message was manually removed from the queue.", e); + } + } + } catch (RuntimeException e) { + logger.error("Error processing queued " + (connectorMessage != null ? connectorMessage.toString() : "message (null)") + " for channel " + channel.getName() + " (" + channel.getChannelId() + ") on destination " + destinationName + ". This error is expected if the message was manually removed from the queue.", e); + /* + * Invalidate the queue's buffer if any errors occurred. If the message + * being processed by the queue was deleted, this will prevent the queue + * from trying to process that message repeatedly. Since multiple + * queues/threads may need to do this as well, we do not reset the queue's + * maps of checked in or deleted messages. + */ + exceptionCaught = true; + } catch (InterruptedException e) { + // Stop this thread if it was halted + return; + } catch (Throwable t) { + // Send a different error message to the server log, but still invalidate the queue buffer + logger.error("Error processing queued " + (connectorMessage != null ? connectorMessage.toString() : "message (null)") + " for channel " + channel.getName() + " (" + channel.getChannelId() + ") on destination " + destinationName + ".", t); + getChannel().getEventDispatcher().dispatchEvent(new ErrorEvent(getChannelId(), getMetaDataId(), connectorMessage != null ? connectorMessage.getMessageId() : null, ErrorEventType.DESTINATION_CONNECTOR, getDestinationName(), getConnectorProperties().getName(), t.getMessage(), t)); + exceptionCaught = true; + } finally { + if (dao != null) { + if (!commitSuccess) { + try { + dao.rollback(); + } catch (Exception e) {} + } + dao.close(); + } - if (response == null) { - throw new RuntimeException("Received null response from destination " + destinationName + "."); + /* + * We always want to release the message if it's done (obviously). + */ + if (exceptionCaught) { + /* + * If an runtime exception was caught, we can't guarantee whether that + * message was deleted or is still in the database. When it is released, + * the message will be removed from the in-memory queue. However we need + * to invalidate the queue before allowing any other threads to be able + * to access it in case the message is still in the database. + */ + canAcquire = true; + synchronized (queue) { + queue.release(connectorMessage, true); + + // Release the read lock now before calling invalidate + if (statusUpdateLock != null) { + statusUpdateLock.unlock(); + statusUpdateLock = null; } - response.fixStatus(isQueueEnabled()); - afterSend(dao, connectorMessage, response, previousStatus); + queue.invalidate(true, false); } + } else if (connectorMessage.getStatus() != Status.QUEUED) { + canAcquire = true; + queue.release(connectorMessage, true); + } else if (destinationConnectorProperties.isRotate()) { + canAcquire = true; + queue.release(connectorMessage, false); + } else { + /* + * If the message is still queued, no exception occurred, and queue + * rotation is disabled, we still want to force the queue to re-acquire + * a message if it has been marked as deleted by another process. + */ + canAcquire = queue.releaseIfDeleted(connectorMessage); } - } else { - connectorMessage.setStatus(Status.ERROR); - connectorMessage.setProcessingError("Mismatched connector properties detected in queued message. The connector type may have changed since the message was queued.\nFOUND: " + serializedPropertiesClass.getSimpleName() + "\nEXPECTED: " + connectorPropertiesClass.getSimpleName()); - dao.updateStatus(connectorMessage, previousStatus); - dao.updateErrors(connectorMessage); + // Always release the read lock if we obtained it + if (statusUpdateLock != null) { + statusUpdateLock.unlock(); + statusUpdateLock = null; + } } - + } else { /* - * If we're about to commit a non-QUEUED status, we first need to obtain a - * read lock from the queue. This is done so that if something else - * invalidates the queue at the same time, we don't incorrectly decrement - * the size during the release. + * This is necessary because there is no blocking peek. If the queue is empty, + * wait some time to free up the cpu. */ - if (connectorMessage.getStatus() != Status.QUEUED) { - Lock lock = queue.getStatusUpdateLock(); - lock.lock(); - statusUpdateLock = lock; - } + Thread.sleep(queueEmptySleepTime); + } + } catch (InterruptedException e) { + // Stop this thread if it was halted + return; + } catch (Throwable t) { + // Always release the read lock if we obtained it + if (statusUpdateLock != null) { + statusUpdateLock.unlock(); + statusUpdateLock = null; + } - ThreadUtils.checkInterruptedStatus(); - dao.commit(storageSettings.isDurable()); - commitSuccess = true; + logger.error("Error in queue thread for channel " + channel.getName() + " (" + channel.getChannelId() + ") on destination " + destinationName + ".\n" + ExceptionUtils.getStackTrace(t)); + getChannel().getEventDispatcher().dispatchEvent(new ErrorEvent(getChannelId(), getMetaDataId(), null, ErrorEventType.DESTINATION_CONNECTOR, getDestinationName(), getConnectorProperties().getName(), t.getMessage(), t)); + + try { + waitingRetryInterval.set(true); + Thread.sleep(retryIntervalMillis); - // Only actually attempt to remove content if the status is SENT - if (connectorMessage.getStatus().isCompleted()) { - try { - channel.removeContent(dao, null, lastMessageId, true, true); - } catch (RuntimeException e) { - /* - * The connector message itself processed successfully, only the - * remove content operation failed. In this case just give up and - * log an error. - */ - logger.error("Error removing content for message " + lastMessageId + " for channel " + channel.getName() + " (" + channel.getChannelId() + ") on destination " + destinationName + ". This error is expected if the message was manually removed from the queue.", e); - } - } - } catch (RuntimeException e) { - logger.error("Error processing queued " + (connectorMessage != null ? connectorMessage.toString() : "message (null)") + " for channel " + channel.getName() + " (" + channel.getChannelId() + ") on destination " + destinationName + ". This error is expected if the message was manually removed from the queue.", e); /* - * Invalidate the queue's buffer if any errors occurred. If the message - * being processed by the queue was deleted, this will prevent the queue - * from trying to process that message repeatedly. Since multiple - * queues/threads may need to do this as well, we do not reset the queue's - * maps of checked in or deleted messages. + * Since the thread already slept for the retry interval, set lastMessageId to + * null to prevent sleeping again. */ - exceptionCaught = true; - } catch (InterruptedException e) { + lastMessageId = null; + } catch (InterruptedException e1) { // Stop this thread if it was halted return; - } catch (Throwable t) { - // Send a different error message to the server log, but still invalidate the queue buffer - logger.error("Error processing queued " + (connectorMessage != null ? connectorMessage.toString() : "message (null)") + " for channel " + channel.getName() + " (" + channel.getChannelId() + ") on destination " + destinationName + ".", t); - getChannel().getEventDispatcher().dispatchEvent(new ErrorEvent(getChannelId(), getMetaDataId(), connectorMessage != null ? connectorMessage.getMessageId() : null, ErrorEventType.DESTINATION_CONNECTOR, getDestinationName(), getConnectorProperties().getName(), t.getMessage(), t)); - exceptionCaught = true; } finally { - if (dao != null) { - if (!commitSuccess) { - try { - dao.rollback(); - } catch (Exception e) {} - } - dao.close(); - } - - /* - * We always want to release the message if it's done (obviously). - */ - if (exceptionCaught) { - /* - * If an runtime exception was caught, we can't guarantee whether that - * message was deleted or is still in the database. When it is released, - * the message will be removed from the in-memory queue. However we need - * to invalidate the queue before allowing any other threads to be able - * to access it in case the message is still in the database. - */ - canAcquire = true; - synchronized (queue) { - queue.release(connectorMessage, true); - - // Release the read lock now before calling invalidate - if (statusUpdateLock != null) { - statusUpdateLock.unlock(); - statusUpdateLock = null; - } - - queue.invalidate(true, false); - } - } else if (connectorMessage.getStatus() != Status.QUEUED) { - canAcquire = true; - queue.release(connectorMessage, true); - } else if (destinationConnectorProperties.isRotate()) { - canAcquire = true; - queue.release(connectorMessage, false); - } else { - /* - * If the message is still queued, no exception occurred, and queue - * rotation is disabled, we still want to force the queue to re-acquire - * a message if it has been marked as deleted by another process. - */ - canAcquire = queue.releaseIfDeleted(connectorMessage); - } - - // Always release the read lock if we obtained it - if (statusUpdateLock != null) { - statusUpdateLock.unlock(); - statusUpdateLock = null; + synchronized (waitingRetryInterval) { + waitingRetryInterval.set(false); } } - } else { - /* - * This is necessary because there is no blocking peek. If the queue is empty, - * wait some time to free up the cpu. - */ - Thread.sleep(queueEmptySleepTime); - } - } catch (InterruptedException e) { - // Stop this thread if it was halted - return; - } catch (Throwable t) { - // Always release the read lock if we obtained it - if (statusUpdateLock != null) { - statusUpdateLock.unlock(); - statusUpdateLock = null; - } - - logger.error("Error in queue thread for channel " + channel.getName() + " (" + channel.getChannelId() + ") on destination " + destinationName + ".\n" + ExceptionUtils.getStackTrace(t)); - getChannel().getEventDispatcher().dispatchEvent(new ErrorEvent(getChannelId(), getMetaDataId(), null, ErrorEventType.DESTINATION_CONNECTOR, getDestinationName(), getConnectorProperties().getName(), t.getMessage(), t)); - - try { - waitingRetryInterval.set(true); - Thread.sleep(retryIntervalMillis); - - /* - * Since the thread already slept for the retry interval, set lastMessageId to - * null to prevent sleeping again. - */ - lastMessageId = null; - } catch (InterruptedException e1) { - // Stop this thread if it was halted - return; } finally { - synchronized (waitingRetryInterval) { - waitingRetryInterval.set(false); + // Always release the read lock if we obtained it + if (statusUpdateLock != null) { + statusUpdateLock.unlock(); + statusUpdateLock = null; } } - } finally { - // Always release the read lock if we obtained it - if (statusUpdateLock != null) { - statusUpdateLock.unlock(); - statusUpdateLock = null; - } - } - } while ((getCurrentState() == DeployedState.STARTED || getCurrentState() == DeployedState.STARTING) && !stopQueue.get()); + } while ((getCurrentState() == DeployedState.STARTED || getCurrentState() == DeployedState.STARTING) && !stopQueue.get()); } } diff --git a/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java b/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java index 9f616fa88a..7534a656b8 100644 --- a/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java +++ b/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java @@ -12,6 +12,7 @@ import java.util.concurrent.Callable; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.CloseableThreadContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; @@ -115,24 +116,14 @@ protected Context getContext() { @Override public final T call() throws Exception { String originalThreadName = Thread.currentThread().getName(); - try { + try (CloseableThreadContext.Instance ctc = CloseableThreadContext + .put("channelId", channelId != null ? channelId : "") + .put("channelName", channelName != null ? channelName : "") + ) { Thread.currentThread().setName(threadName + " < " + originalThreadName); - if (channelId != null) { - ThreadContext.put("channelId", channelId); - } - if (channelName != null) { - ThreadContext.put("channelName", channelName); - } - return doCall(); } finally { - if (channelName != null) { - ThreadContext.remove("channelName"); - } - if (channelId != null) { - ThreadContext.remove("channelId"); - } Thread.currentThread().setName(originalThreadName); } } From 80e1b2e10e09502d6f7baca6d95a8197dac1e262 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Thu, 20 Nov 2025 14:43:17 +0100 Subject: [PATCH 10/24] Reverted to original Signed-off-by: Nico Piel --- server/build.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/server/build.xml b/server/build.xml index 92bfa030b8..1e66e78173 100644 --- a/server/build.xml +++ b/server/build.xml @@ -888,7 +888,6 @@ - From 74672e6e97940026fc0b105a93a993dd36bf168d Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Thu, 20 Nov 2025 14:45:52 +0100 Subject: [PATCH 11/24] Use getContextData instead of getContextMap Signed-off-by: Nico Piel --- .../mirth/connect/plugins/serverlog/ArrayAppender.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java index 586ae2892c..6a547f92e6 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java @@ -39,11 +39,9 @@ public void append(LogEvent logEvent) { String channelId = null; String channelName = null; - // Check theoretically not necessary, as getContextMap never returns null - // Safety first - if (logEvent.getContextMap() != null) { - channelId = logEvent.getContextMap().getOrDefault("channelId", ""); - channelName = logEvent.getContextMap().getOrDefault("channelName", ""); + if (logEvent.getContextData() != null) { + channelId = logEvent.getContextData().getValue("channelId"); + channelName = logEvent.getContextData().getValue("channelName"); } String level = String.valueOf(logEvent.getLevel()); From c28a7053244fcd2fa81d9ee28a01932135858ec3 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Thu, 20 Nov 2025 14:51:33 +0100 Subject: [PATCH 12/24] Refactors server log handling for improved efficiency Changes the ArrayAppender to construct a ServerLogItem directly instead of passing individual log properties. The ServerLogProvider now accepts a ServerLogItem, extracts the log properties, adds the server ID and log ID, and then passes the log item to the log controller. This change simplifies the code and makes it more efficient by reducing the number of parameters passed and centralizing the creation of the ServerLogItem. Signed-off-by: Nico Piel --- .../plugins/serverlog/ArrayAppender.java | 15 +++++++++++++- .../plugins/serverlog/ServerLogProvider.java | 20 +++++-------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java index 6a547f92e6..bcab3b2702 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java @@ -11,6 +11,8 @@ import java.io.Serializable; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; @@ -69,6 +71,17 @@ public void append(LogEvent logEvent) { throwableInformation = logText.toString(); } - serverLogProvider.newServerLogReceived(channelId, channelName, level, date, threadName, category, lineNumber, message, throwableInformation); + Map properties = new HashMap<>(); + properties.put("channelId", channelId); + properties.put("channelName", channelName); + properties.put("level", level); + properties.put("date", date); + properties.put("threadName", threadName); + properties.put("category", category); + properties.put("lineNumber", lineNumber); + properties.put("message", message); + properties.put("throwableInformation", throwableInformation); + + serverLogProvider.newServerLogReceived(new ServerLogItem(properties)); } } diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java index eae755ca39..b5b60dd250 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java @@ -57,22 +57,12 @@ public void init(Properties properties) { initialize(); } - public synchronized void newServerLogReceived(String channelId, String channelName, String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation) { + public synchronized void newServerLogReceived(ServerLogItem svi) { if (logController != null) { - Map properties = new HashMap<>(); - properties.put("serverId", serverId); - properties.put("id", logId); - properties.put("channelId", channelId); - properties.put("channelName", channelName); - properties.put("level", level); - properties.put("date", date); - properties.put("threadName", threadName); - properties.put("category", category); - properties.put("lineNumber", lineNumber); - properties.put("message", message); - properties.put("throwableInformation", throwableInformation); - - logController.addLogItem(new ServerLogItem(properties)); + svi.getContext().put("serverId", serverId); + svi.getContext().put("id", logId); + + logController.addLogItem(svi); logId++; } } From 53a85d968de08588c263638caed8e08bf6146e5e Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Thu, 20 Nov 2025 14:55:48 +0100 Subject: [PATCH 13/24] Sets default values for ServerLogItem Ensures that the ServerLogItem properties are initialized with default values when retrieving them from the context. This prevents null pointer exceptions or unexpected behavior when these properties are not explicitly set in the context. Signed-off-by: Nico Piel --- .../com/mirth/connect/plugins/serverlog/ServerLogItem.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java index 83b42a0c0d..ef1183f34f 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java @@ -11,6 +11,7 @@ import java.io.Serializable; import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -44,11 +45,11 @@ public ServerLogItem(Map properties) { } this.serverId = (String) context.getOrDefault("serverId", ""); - this.id = (Long) context.getOrDefault("id", ""); + this.id = (Long) context.getOrDefault("id", 0L); this.channelId = (String) context.getOrDefault("channelId", ""); this.channelName = (String) context.getOrDefault("channelName", ""); this.level = (String) context.getOrDefault("level", ""); - this.date = (Date) context.getOrDefault("date", ""); + this.date = (Date) context.getOrDefault("date", Date.from(Instant.now())); this.threadName = (String) context.getOrDefault("threadName", ""); this.category = (String) context.getOrDefault("category", ""); this.lineNumber = (String) context.getOrDefault("lineNumber", ""); From 278d2b32f23f188b749adba99fc596137a16bf41 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 21:06:51 +0100 Subject: [PATCH 14/24] Implemented old ServerLogItem + an attributes map Signed-off-by: Nico Piel --- .../plugins/serverlog/ServerLogClient.java | 4 +- .../plugins/serverlog/ArrayAppender.java | 21 +---- .../plugins/serverlog/ServerLogItem.java | 85 ++++++++++--------- .../plugins/serverlog/ServerLogProvider.java | 7 +- .../servlets/SwaggerExamplesServlet.java | 14 +-- 5 files changed, 50 insertions(+), 81 deletions(-) diff --git a/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java b/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java index c4e0d63bbc..95131191d8 100644 --- a/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java +++ b/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java @@ -24,9 +24,7 @@ public class ServerLogClient extends DashboardTabPlugin { private ServerLogPanel serverLogPanel; private LinkedList serverLogs; - private static final ServerLogItem unauthorizedLog = new ServerLogItem(new HashMap(){{ - put("message", "You are not authorized to view the server log."); - }}); + private static final ServerLogItem unauthorizedLog = new ServerLogItem("You are not authorized to view the server log."); private int currentServerLogSize; private boolean receivedNewLogs; private Long lastLogId; diff --git a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java index bcab3b2702..48d47d44d5 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java @@ -38,14 +38,6 @@ public void append(LogEvent logEvent) { return; } - String channelId = null; - String channelName = null; - - if (logEvent.getContextData() != null) { - channelId = logEvent.getContextData().getValue("channelId"); - channelName = logEvent.getContextData().getValue("channelName"); - } - String level = String.valueOf(logEvent.getLevel()); Date date = new Date(logEvent.getTimeMillis()); String threadName = logEvent.getThreadName(); @@ -71,17 +63,6 @@ public void append(LogEvent logEvent) { throwableInformation = logText.toString(); } - Map properties = new HashMap<>(); - properties.put("channelId", channelId); - properties.put("channelName", channelName); - properties.put("level", level); - properties.put("date", date); - properties.put("threadName", threadName); - properties.put("category", category); - properties.put("lineNumber", lineNumber); - properties.put("message", message); - properties.put("throwableInformation", throwableInformation); - - serverLogProvider.newServerLogReceived(new ServerLogItem(properties)); + serverLogProvider.newServerLogReceived(level, date, threadName, category, lineNumber, message, throwableInformation, logEvent.getContextData().toMap()); } } diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java index ef1183f34f..535fa632ad 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java @@ -24,7 +24,6 @@ public class ServerLogItem implements Serializable { private String serverId; private Long id; - private String channelId; private String level; private Date date; private String threadName; @@ -32,29 +31,31 @@ public class ServerLogItem implements Serializable { private String lineNumber; private String message; private String throwableInformation; - private String channelName; - private HashMap context; + private Map attributes; - public ServerLogItem() {} + public ServerLogItem() { + this(null, null, null, null, null, null, null, null, null, new HashMap<>()); + } - public ServerLogItem(Map properties) { - if (properties != null) { - this.context = new HashMap<>(properties); - } else { - this.context = new HashMap<>(); - } + public ServerLogItem(String message) { + this(null, null, null, null, null, null, null, message, null, new HashMap<>()); + } - this.serverId = (String) context.getOrDefault("serverId", ""); - this.id = (Long) context.getOrDefault("id", 0L); - this.channelId = (String) context.getOrDefault("channelId", ""); - this.channelName = (String) context.getOrDefault("channelName", ""); - this.level = (String) context.getOrDefault("level", ""); - this.date = (Date) context.getOrDefault("date", Date.from(Instant.now())); - this.threadName = (String) context.getOrDefault("threadName", ""); - this.category = (String) context.getOrDefault("category", ""); - this.lineNumber = (String) context.getOrDefault("lineNumber", ""); - this.message = (String) context.getOrDefault("message", ""); - this.throwableInformation = (String) context.getOrDefault("throwableInformation", ""); + public ServerLogItem(String serverId, Long id, String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation) { + this(serverId, id, level, date, threadName, category, lineNumber, message, throwableInformation, new HashMap<>()); + } + + public ServerLogItem(String serverId, Long id, String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation, Map attributes) { + this.serverId = serverId; + this.id = id; + this.level = level; + this.date = date; + this.threadName = threadName; + this.category = category; + this.lineNumber = lineNumber; + this.message = message; + this.throwableInformation = throwableInformation; + this.attributes = attributes; } public String getServerId() { @@ -72,14 +73,6 @@ public Long getId() { public void setId(Long id) { this.id = id; } - - public String getChannelId() { - return channelId; - } - - public void setChannelId(String channelId) { - this.channelId = channelId; - } public String getLevel() { return level; @@ -136,21 +129,33 @@ public String getThrowableInformation() { public void setThrowableInformation(String throwableInformation) { this.throwableInformation = throwableInformation; } - - public String getChannelName() { - return channelName; + + public Map getAttributes() { + return attributes; } - - public void setChannelName(String channelName) { - this.channelName = channelName; + + public void setAttributes(Map attributes) { + if (attributes == null) { + this.attributes = new HashMap(); + } else { + this.attributes = attributes; + } } - - public Map getContext() { - return context; + + public String getChannelId() { + return this.attributes.get("channelId"); + } + + public void setChannelId(String channelId) { + this.attributes.put("channelId", channelId); } - public void setContext(Map context) { - this.context = new HashMap<>(context); + public String getChannelName() { + return this.attributes.get("channelName"); + } + + public void setChannelName(String channelName) { + this.attributes.put("channelName", channelName); } @Override diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java index b5b60dd250..d6bd31080a 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java @@ -57,12 +57,9 @@ public void init(Properties properties) { initialize(); } - public synchronized void newServerLogReceived(ServerLogItem svi) { + public synchronized void newServerLogReceived(String level, Date date, String threadName, String category, String lineNumber, String message, String throwableInformation, Map attributes) { if (logController != null) { - svi.getContext().put("serverId", serverId); - svi.getContext().put("id", logId); - - logController.addLogItem(svi); + logController.addLogItem(new ServerLogItem(serverId, logId, level, date, threadName, category, lineNumber, message, throwableInformation, attributes)); logId++; } } diff --git a/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java b/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java index e8e9b9af1d..021b909053 100644 --- a/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java +++ b/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java @@ -1286,19 +1286,7 @@ private List getServerEventListExample() { } private ServerLogItem getServerLogItemExample() { - // return new ServerLogItem(UUID.randomUUID().toString(), 1L, "INFO", dateNow.getTime(), "Main Server Thread", "com.mirth.connect.server.Mirth", "1", "Example message", "Example throwable information"); - Map properties = new HashMap<>(); - properties.put("serverId", UUID.randomUUID().toString()); - properties.put("id", 1L); - properties.put("channelId", "INFO"); - properties.put("date", dateNow.getTime()); - properties.put("threadName", "Main Server Thread"); - properties.put("category", "com.mirth.connect.server.Mirth"); - properties.put("lineNumber", "1"); - properties.put("message", "Example Message"); - properties.put("throwableInformation", "Example throwable Information"); - - return new ServerLogItem(properties); + return new ServerLogItem(UUID.randomUUID().toString(), 1L, "INFO", dateNow.getTime(), "Main Server Thread", "com.mirth.connect.server.Mirth", "1", "Example message", "Example throwable information"); } private List getServerLogItemListExample() { From b1623c9858a6344e269ad2052d978b5caec01461 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 21:12:26 +0100 Subject: [PATCH 15/24] Removed unused imports Signed-off-by: Nico Piel --- .../src/com/mirth/connect/plugins/serverlog/ArrayAppender.java | 2 -- .../src/com/mirth/connect/plugins/serverlog/ServerLogItem.java | 1 - .../com/mirth/connect/plugins/serverlog/ServerLogProvider.java | 1 - 3 files changed, 4 deletions(-) diff --git a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java index 48d47d44d5..d58f350c4c 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ArrayAppender.java @@ -11,8 +11,6 @@ import java.io.Serializable; import java.util.Date; -import java.util.HashMap; -import java.util.Map; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java index 535fa632ad..26344f9a95 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java @@ -11,7 +11,6 @@ import java.io.Serializable; import java.text.SimpleDateFormat; -import java.time.Instant; import java.util.Date; import java.util.HashMap; import java.util.Map; diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java index d6bd31080a..3df7cba8fa 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogProvider.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; From 6c5fbc6e1b74664b0e81928719d312b4adaa19d5 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 21:34:26 +0100 Subject: [PATCH 16/24] Revert ServerLogClient.java Signed-off-by: Nico Piel --- .../com/mirth/connect/plugins/serverlog/ServerLogClient.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java b/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java index 95131191d8..f07ebf3328 100644 --- a/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java +++ b/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java @@ -9,7 +9,9 @@ package com.mirth.connect.plugins.serverlog; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; import javax.swing.JComponent; @@ -167,3 +169,4 @@ public String getPluginPointName() { return ServerLogServletInterface.PLUGIN_POINT; } } + From 9f833e3645e9db848a613e61d714330fac5443c6 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 21:34:56 +0100 Subject: [PATCH 17/24] Update ServerLogClient.java Signed-off-by: Nico Piel --- .../src/com/mirth/connect/plugins/serverlog/ServerLogClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java b/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java index f07ebf3328..36e93ca315 100644 --- a/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java +++ b/client/src/com/mirth/connect/plugins/serverlog/ServerLogClient.java @@ -169,4 +169,3 @@ public String getPluginPointName() { return ServerLogServletInterface.PLUGIN_POINT; } } - From a7b02967204f42cdb64771a4a168156f0a4d719a Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 21:39:11 +0100 Subject: [PATCH 18/24] Exclude channel attributes from server log JSON Signed-off-by: Nico Piel --- .../com/mirth/connect/donkey/server/channel/Channel.java | 1 - .../com/mirth/connect/plugins/serverlog/ServerLogItem.java | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java index 4ad283e926..800ca121bd 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java @@ -41,7 +41,6 @@ import org.apache.logging.log4j.CloseableThreadContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.ThreadContext; import com.mirth.connect.donkey.model.DonkeyException; import com.mirth.connect.donkey.model.channel.DebugOptions; diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java index 26344f9a95..705c7f0ff7 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java @@ -15,6 +15,7 @@ import java.util.HashMap; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.apache.commons.lang3.StringUtils; public class ServerLogItem implements Serializable { @@ -141,18 +142,22 @@ public void setAttributes(Map attributes) { } } + @JsonIgnore public String getChannelId() { return this.attributes.get("channelId"); } + @JsonIgnore public void setChannelId(String channelId) { this.attributes.put("channelId", channelId); } + @JsonIgnore public String getChannelName() { return this.attributes.get("channelName"); } + @JsonIgnore public void setChannelName(String channelName) { this.attributes.put("channelName", channelName); } From 9d0946b26b45ed7144a4b43e944f0c3a1bb68c0c Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 21:42:05 +0100 Subject: [PATCH 19/24] Revert SwaggerExamplesServlet Signed-off-by: Nico Piel --- .../mirth/connect/server/servlets/SwaggerExamplesServlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java b/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java index 021b909053..476775c82b 100644 --- a/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java +++ b/server/src/com/mirth/connect/server/servlets/SwaggerExamplesServlet.java @@ -1287,7 +1287,7 @@ private List getServerEventListExample() { private ServerLogItem getServerLogItemExample() { return new ServerLogItem(UUID.randomUUID().toString(), 1L, "INFO", dateNow.getTime(), "Main Server Thread", "com.mirth.connect.server.Mirth", "1", "Example message", "Example throwable information"); - } + } private List getServerLogItemListExample() { List serverLogList = new ArrayList<>(); From d0b358f7f2a46e1fc0959434a11e543b768580fb Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 21:47:00 +0100 Subject: [PATCH 20/24] Line wrap Signed-off-by: Nico Piel --- .../connect/donkey/server/channel/DestinationConnector.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java index dfcf3125b3..8f040bcd8f 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java @@ -616,7 +616,10 @@ public void processPendingConnectorMessage(DonkeyDao dao, ConnectorMessage messa @Override public void run() { // Add channel info to ThreadContext - try (CloseableThreadContext.Instance ctc = CloseableThreadContext.put("channelId", getChannelId()).put("channelName", channel.getName())) { + try (CloseableThreadContext.Instance ctc = CloseableThreadContext + .put("channelId", getChannelId()) + .put("channelName", channel.getName()) + ) { DonkeyDao dao = null; boolean commitSuccess = false; Serializer serializer = channel.getSerializer(); From b272f5dcea2c54630469ac861afd00e4dd0d5829 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 21:47:23 +0100 Subject: [PATCH 21/24] Removed unused import Signed-off-by: Nico Piel --- .../connect/donkey/server/channel/DestinationConnector.java | 1 - 1 file changed, 1 deletion(-) diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java index 8f040bcd8f..ec675fca19 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationConnector.java @@ -25,7 +25,6 @@ import org.apache.logging.log4j.CloseableThreadContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.ThreadContext; import com.mirth.connect.donkey.model.DonkeyException; import com.mirth.connect.donkey.model.channel.ConnectorProperties; From 6ec3d04b2f012a2c8f2b80588af8e9cdb420308a Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 22:33:02 +0100 Subject: [PATCH 22/24] Added channel name to DestinationChain higher up the call chain Signed-off-by: Nico Piel --- .../donkey/server/channel/Channel.java | 1 + .../server/channel/DestinationChain.java | 28 +++++++++---------- .../channel/DestinationChainProvider.java | 11 +++++++- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java index 800ca121bd..6f19810239 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/Channel.java @@ -536,6 +536,7 @@ public synchronized void deploy(DebugOptions debugOptions) throws DeployExceptio for (DestinationChainProvider chainProvider : destinationChainProviders) { chainProvider.setDaoFactory(daoFactory); chainProvider.setStorageSettings(storageSettings); + chainProvider.setChannelName(name); for (Integer metaDataId : chainProvider.getMetaDataIds()) { DestinationConnector destinationConnector = chainProvider.getDestinationConnectors().get(metaDataId); diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java index c48b6dec74..fd4897e9d9 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java @@ -35,11 +35,15 @@ public class DestinationChain implements Callable> { private List enabledMetaDataIds = new ArrayList(); private Logger logger = LogManager.getLogger(getClass()); private String name; + private String channelId; + private String channelName; public DestinationChain(DestinationChainProvider chainProvider) { this.chainProvider = chainProvider; enabledMetaDataIds = new ArrayList(chainProvider.getMetaDataIds()); - name = "Destination Chain Thread on " + chainProvider.getChannelId(); + channelId = chainProvider.getChannelId(); + channelName = chainProvider.getChannelName(); + name = "Destination Chain Thread on " + channelId; } public void setMessage(ConnectorMessage message) { @@ -61,23 +65,17 @@ public void setName(String name) { @Override public List call() throws InterruptedException { String originalThreadName = Thread.currentThread().getName(); - try { - Thread.currentThread().setName(name + " < " + originalThreadName); - String channelId = chainProvider.getChannelId(); - String channelName = null; - if (!chainProvider.getDestinationConnectors().isEmpty()) { - channelName = chainProvider.getDestinationConnectors().values().iterator().next().getChannel().getName(); - } - - try (CloseableThreadContext.Instance ctc = CloseableThreadContext - .put("channelId", channelId) - .put("channelName", channelName != null ? channelName : "") - ) { + try (CloseableThreadContext.Instance ctc = CloseableThreadContext + .put("channelId", channelId) + .put("channelName", channelName != null ? channelName : "") + ) { + try { + Thread.currentThread().setName(name + " < " + originalThreadName); return doCall(); + } finally { + Thread.currentThread().setName(originalThreadName); } - } finally { - Thread.currentThread().setName(originalThreadName); } } diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChainProvider.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChainProvider.java index 31088d5f1c..7f174cdfa9 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChainProvider.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChainProvider.java @@ -19,6 +19,7 @@ public class DestinationChainProvider { private Integer chainId; private String channelId; + private String channelName; private List metaDataIds = new ArrayList(); private Map destinationConnectors = new LinkedHashMap(); private DonkeyDaoFactory daoFactory; @@ -40,6 +41,14 @@ public void setChannelId(String channelId) { this.channelId = channelId; } + public String getChannelName() { + return channelName; + } + + public void setChannelName(String channelName) { + this.channelName = channelName; + } + public void addDestination(int metaDataId, DestinationConnector connector) { if (!metaDataIds.contains(metaDataId)) { metaDataIds.add(metaDataId); @@ -76,4 +85,4 @@ protected void setStorageSettings(StorageSettings storageSettings) { public DestinationChain getChain() { return new DestinationChain(this); } -} \ No newline at end of file +} From 22b0669e9986db8a1566e386967ef326a702330f Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 22:36:46 +0100 Subject: [PATCH 23/24] Removed unncessary null checks Signed-off-by: Nico Piel --- .../mirth/connect/server/util/javascript/JavaScriptTask.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java b/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java index 7534a656b8..119d785697 100644 --- a/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java +++ b/server/src/com/mirth/connect/server/util/javascript/JavaScriptTask.java @@ -117,8 +117,8 @@ protected Context getContext() { public final T call() throws Exception { String originalThreadName = Thread.currentThread().getName(); try (CloseableThreadContext.Instance ctc = CloseableThreadContext - .put("channelId", channelId != null ? channelId : "") - .put("channelName", channelName != null ? channelName : "") + .put("channelId", channelId) + .put("channelName", channelName) ) { Thread.currentThread().setName(threadName + " < " + originalThreadName); From f1f1f4f0517ebcb96c4d1010ed81eaf94463b1a3 Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Mon, 24 Nov 2025 22:56:37 +0100 Subject: [PATCH 24/24] Omit unnecessary null check Signed-off-by: Nico Piel --- .../mirth/connect/donkey/server/channel/DestinationChain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java index fd4897e9d9..2b0367c7d7 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/DestinationChain.java @@ -68,7 +68,7 @@ public List call() throws InterruptedException { try (CloseableThreadContext.Instance ctc = CloseableThreadContext .put("channelId", channelId) - .put("channelName", channelName != null ? channelName : "") + .put("channelName", channelName) ) { try { Thread.currentThread().setName(name + " < " + originalThreadName);