Skip to content

Commit a460488

Browse files
Merge pull request #2230 from SanojPunchihewa/log-mediator
Improve log mediator to support string templating
2 parents b5a55c1 + 2463b2c commit a460488

File tree

7 files changed

+205
-37
lines changed

7 files changed

+205
-37
lines changed

modules/core/src/main/java/org/apache/synapse/config/xml/LogMediatorFactory.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
*
3333
* <pre>
3434
* &lt;log [level="simple|headers|full|custom"]&gt;
35+
* &lt;message&gt;String template&lt;/message&gt;
3536
* &lt;property&gt; *
3637
* &lt;/log&gt;
3738
* </pre>
@@ -52,6 +53,8 @@ public class LogMediatorFactory extends AbstractMediatorFactory {
5253
private static final QName ATT_LEVEL = new QName("level");
5354
private static final QName ATT_SEPERATOR = new QName("separator");
5455
private static final QName ATT_CATEGORY = new QName("category");
56+
protected static final QName ELEMENT_MESSAGE_Q
57+
= new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "message");
5558

5659
public QName getTagQName() {
5760
return LOG_Q;
@@ -64,7 +67,14 @@ public Mediator createSpecificMediator(OMElement elem, Properties properties) {
6467
// after successfully creating the mediator
6568
// set its common attributes such as tracing etc
6669
processAuditStatus(logMediator,elem);
67-
70+
71+
boolean containMessageTemplate = false;
72+
OMElement messageElement = elem.getFirstChildWithName(ELEMENT_MESSAGE_Q);
73+
if (messageElement != null && messageElement.getText() != null) {
74+
logMediator.setMessageTemplate(messageElement.getText());
75+
containMessageTemplate = true;
76+
}
77+
6878
// Set the high level set of properties to be logged (i.e. log level)
6979
OMAttribute level = elem.getAttribute(ATT_LEVEL);
7080
if (level != null) {
@@ -81,6 +91,10 @@ public Mediator createSpecificMediator(OMElement elem, Properties properties) {
8191
handleException("Invalid log level. Level has to be one of the following : "
8292
+ "simple, headers, full, custom");
8393
}
94+
} else {
95+
if (containMessageTemplate) {
96+
logMediator.setLogLevel(LogMediator.MESSAGE_TEMPLATE);
97+
}
8498
}
8599

86100
// Set the log statement category (i.e. INFO, DEBUG, etc..)
@@ -112,7 +126,7 @@ public Mediator createSpecificMediator(OMElement elem, Properties properties) {
112126
}
113127

114128
logMediator.addAllProperties(MediatorPropertyFactory.getMediatorProperties(elem));
115-
129+
logMediator.processTemplateAndSetContentAware();
116130
addAllCommentChildrenToList(elem, logMediator.getCommentsList());
117131

118132
return logMediator;

modules/core/src/main/java/org/apache/synapse/config/xml/LogMediatorSerializer.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
package org.apache.synapse.config.xml;
2121

2222
import org.apache.axiom.om.OMElement;
23+
import org.apache.commons.lang3.StringUtils;
2324
import org.apache.synapse.Mediator;
2425
import org.apache.synapse.mediators.builtin.LogMediator;
2526

2627
/**
2728
* <pre>
2829
* &lt;log [level="simple|headers|full|custom"] [separator="string"] [category="INFO|TRACE|DEBUG|WARN|ERROR|FATAL"]&gt;
30+
* &lt;message&gt;String template&lt;/message&gt;
2931
* &lt;property&gt; *
3032
* &lt;/log&gt;
3133
* </pre>
@@ -42,7 +44,7 @@ public OMElement serializeSpecificMediator(Mediator m) {
4244
OMElement log = fac.createOMElement("log", synNS);
4345
saveTracingState(log,mediator);
4446

45-
if (mediator.getLogLevel() != LogMediator.SIMPLE) {
47+
if (mediator.getLogLevel() != LogMediator.MESSAGE_TEMPLATE) {
4648
log.addAttribute(fac.createOMAttribute(
4749
"level", nullNS,
4850
mediator.getLogLevel() == LogMediator.HEADERS ? "headers" :
@@ -73,6 +75,12 @@ public OMElement serializeSpecificMediator(Mediator m) {
7375
"separator", nullNS, mediator.getSeparator()));
7476
}
7577

78+
if (StringUtils.isNotBlank(mediator.getMessageTemplate())) {
79+
OMElement onCompleteElem = fac.createOMElement("message", synNS);
80+
onCompleteElem.setText(mediator.getMessageTemplate());
81+
log.addChild(onCompleteElem);
82+
}
83+
7684
super.serializeProperties(log, mediator.getProperties());
7785

7886
serializeComments(log, mediator.getCommentsList());

modules/core/src/main/java/org/apache/synapse/mediators/builtin/LogMediator.java

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@
3232
import org.apache.synapse.core.axis2.Axis2MessageContext;
3333
import org.apache.synapse.mediators.AbstractMediator;
3434
import org.apache.synapse.mediators.MediatorProperty;
35+
import org.apache.synapse.util.InlineExpressionUtil;
36+
import org.jaxen.JaxenException;
3537

3638
import java.util.ArrayList;
3739
import java.util.Iterator;
3840
import java.util.List;
3941

40-
4142
/**
4243
* Logs the specified message into the configured logger. The log levels specify
4344
* which attributes would be logged, and is configurable. Additionally custom
@@ -56,6 +57,9 @@ public class LogMediator extends AbstractMediator {
5657
/** all attributes of level 'simple' and the SOAP envelope and any properties */
5758
public static final int FULL = 3;
5859

60+
/** The message template and the additional properties specified to the Log mediator */
61+
public static final int MESSAGE_TEMPLATE = 4;
62+
5963
public static final int CATEGORY_INFO = 0;
6064
public static final int CATEGORY_DEBUG = 1;
6165
public static final int CATEGORY_TRACE = 2;
@@ -74,6 +78,9 @@ public class LogMediator extends AbstractMediator {
7478
/** The holder for the custom properties */
7579
private final List<MediatorProperty> properties = new ArrayList<MediatorProperty>();
7680

81+
private String messageTemplate = "";
82+
private boolean isContentAware = false;
83+
7784
/**
7885
* Logs the current message according to the supplied semantics
7986
*
@@ -136,6 +143,7 @@ public boolean mediate(MessageContext synCtx) {
136143
private String getLogMessage(MessageContext synCtx) {
137144
switch (logLevel) {
138145
case CUSTOM:
146+
case MESSAGE_TEMPLATE:
139147
return getCustomLogMessage(synCtx);
140148
case SIMPLE:
141149
return getSimpleLogMessage(synCtx);
@@ -150,12 +158,18 @@ private String getLogMessage(MessageContext synCtx) {
150158

151159
private String getCustomLogMessage(MessageContext synCtx) {
152160
StringBuffer sb = new StringBuffer();
161+
processMessageTemplate(sb, synCtx, messageTemplate);
153162
setCustomProperties(sb, synCtx);
154163
return trimLeadingSeparator(sb);
155164
}
156165

157166
private String getSimpleLogMessage(MessageContext synCtx) {
158167
StringBuffer sb = new StringBuffer();
168+
processMessageTemplate(sb, synCtx, messageTemplate);
169+
// append separator if the message template is not empty
170+
if (sb.length() > 0) {
171+
sb.append(separator);
172+
}
159173
if (synCtx.getTo() != null)
160174
sb.append("To: ").append(synCtx.getTo().getAddress());
161175
else
@@ -180,6 +194,7 @@ private String getSimpleLogMessage(MessageContext synCtx) {
180194

181195
private String getHeadersLogMessage(MessageContext synCtx) {
182196
StringBuffer sb = new StringBuffer();
197+
processMessageTemplate(sb, synCtx, messageTemplate);
183198
if (synCtx.getEnvelope() != null) {
184199
SOAPHeader header = synCtx.getEnvelope().getHeader();
185200
if (getCorrelationId(synCtx) != null)
@@ -288,6 +303,16 @@ public void setCategory(int category) {
288303
}
289304
}
290305

306+
public String getMessageTemplate() {
307+
308+
return messageTemplate;
309+
}
310+
311+
public void setMessageTemplate(String messageTemplate) {
312+
313+
this.messageTemplate = messageTemplate.replace("\\n", "\n").replace("\\t", "\t");
314+
}
315+
291316
private String trimLeadingSeparator(StringBuffer sb) {
292317
String retStr = sb.toString();
293318
if (retStr.startsWith(separator)) {
@@ -297,16 +322,32 @@ private String trimLeadingSeparator(StringBuffer sb) {
297322
}
298323
}
299324

325+
private void processMessageTemplate(StringBuffer stringBuffer, MessageContext synCtx, String template) {
326+
try {
327+
stringBuffer.append(InlineExpressionUtil.processInLineSynapseExpressionTemplate(synCtx, template));
328+
} catch (JaxenException e) {
329+
handleException("Failed to process the message template : " + template, e, synCtx);
330+
}
331+
}
332+
300333
@Override
301334
public boolean isContentAware() {
302-
if (logLevel == CUSTOM) {
335+
336+
return isContentAware;
337+
}
338+
339+
public void processTemplateAndSetContentAware() {
340+
341+
if (logLevel == MESSAGE_TEMPLATE || logLevel == CUSTOM) {
303342
for (MediatorProperty property : properties) {
304343
if (property.getExpression() != null && property.getExpression().isContentAware()) {
305-
return true;
344+
isContentAware = true;
345+
return;
306346
}
307347
}
308-
return false;
348+
isContentAware = InlineExpressionUtil.isInlineSynapseExpressionsContentAware(messageTemplate);
349+
} else {
350+
isContentAware = true;
309351
}
310-
return true;
311352
}
312353
}

modules/core/src/main/java/org/apache/synapse/util/InlineExpressionUtil.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import com.google.gson.JsonArray;
2121
import com.google.gson.JsonElement;
22-
import com.google.gson.JsonNull;
2322
import com.google.gson.JsonObject;
2423
import com.google.gson.JsonParser;
2524
import com.google.gson.JsonPrimitive;
@@ -30,6 +29,8 @@
3029
import org.apache.synapse.MessageContext;
3130
import org.apache.synapse.SynapseException;
3231
import org.apache.synapse.config.xml.SynapsePath;
32+
import org.apache.synapse.util.xpath.SynapseExpression;
33+
import org.apache.synapse.util.xpath.SynapseExpressionUtils;
3334
import org.apache.synapse.util.xpath.SynapseJsonPath;
3435
import org.apache.synapse.util.xpath.SynapseXPath;
3536
import org.jaxen.JaxenException;
@@ -49,6 +50,9 @@ public final class InlineExpressionUtil {
4950
// Regex to identify expressions in inline text
5051
private static final Pattern EXPRESSION_PATTERN = Pattern.compile("(\\{[^\\s\",<>}\\]]+})");
5152

53+
// Regex to identify synapse expressions ${expression} in inline text
54+
private static final Pattern SYNAPSE_EXPRESSION_PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{(.+?)}");
55+
5256
private InlineExpressionUtil() {
5357

5458
}
@@ -197,4 +201,48 @@ private static boolean isValidXML(String stringToValidate) {
197201
}
198202
return false;
199203
}
204+
205+
/**
206+
* Checks whether inline template contains content aware synapse expressions.
207+
* Inline expressions will be denoted inside ${}
208+
* e.g.: ${var.var1}, ${payload.element.id}
209+
*
210+
* @param inlineText Inline text string
211+
* @return true if the string contains content aware inline synapse expressions, false otherwise
212+
*/
213+
public static boolean isInlineSynapseExpressionsContentAware(String inlineText) {
214+
215+
Matcher matcher = SYNAPSE_EXPRESSION_PLACEHOLDER_PATTERN.matcher(inlineText);
216+
while (matcher.find()) {
217+
// Extract the expression inside ${...}
218+
String expression = matcher.group(1);
219+
if (SynapseExpressionUtils.isSynapseExpressionContentAware(expression)) {
220+
return true;
221+
}
222+
}
223+
return false;
224+
}
225+
226+
/**
227+
* Process the inline template and replace the synapse expressions with the resolved values
228+
*
229+
* @param synCtx Message Context
230+
* @param template Inline template
231+
* @return Processed inline template
232+
*/
233+
public static String processInLineSynapseExpressionTemplate(MessageContext synCtx, String template)
234+
throws JaxenException {
235+
236+
Matcher matcher = SYNAPSE_EXPRESSION_PLACEHOLDER_PATTERN.matcher(template);
237+
StringBuffer result = new StringBuffer();
238+
while (matcher.find()) {
239+
// Extract the expression inside ${...}
240+
String placeholder = matcher.group(1);
241+
SynapseExpression expression = new SynapseExpression(placeholder);
242+
String replacement = expression.stringValueOf(synCtx);
243+
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
244+
}
245+
matcher.appendTail(result);
246+
return result.toString();
247+
}
200248
}

modules/core/src/main/java/org/apache/synapse/util/xpath/SynapseExpression.java

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -74,33 +74,7 @@ public SynapseExpression(String synapseExpression) throws JaxenException {
7474
}
7575
throw new JaxenException(errorMessage.toString());
7676
}
77-
78-
// TODO : Need to improve the content aware detection logic
79-
if (synapseExpression.equals("payload") || synapseExpression.equals("$")
80-
|| synapseExpression.contains("payload.") || synapseExpression.contains("$.")) {
81-
isContentAware = true;
82-
} else if (synapseExpression.contains("xpath(")) {
83-
// TODO change the regex to support xpath + variable syntax
84-
Pattern pattern = Pattern.compile("xpath\\(['\"](.*?)['\"]\\s*(,\\s*['\"](.*?)['\"])?\\)?");
85-
Matcher matcher = pattern.matcher(synapseExpression);
86-
// Find all matches
87-
while (matcher.find()) {
88-
if (matcher.group(2) != null) {
89-
// evaluating xpath on a variable so not content aware
90-
continue;
91-
}
92-
String xpath = matcher.group(1);
93-
try {
94-
SynapseXPath synapseXPath = new SynapseXPath(xpath);
95-
if (synapseXPath.isContentAware()) {
96-
isContentAware = true;
97-
break;
98-
}
99-
} catch (JaxenException e) {
100-
// Ignore the exception and continue
101-
}
102-
}
103-
}
77+
isContentAware = SynapseExpressionUtils.isSynapseExpressionContentAware(synapseExpression);
10478
}
10579

10680
@Override
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
package org.apache.synapse.util.xpath;
20+
21+
import org.jaxen.JaxenException;
22+
23+
import java.util.regex.Matcher;
24+
import java.util.regex.Pattern;
25+
26+
/**
27+
* Utility class for Synapse Expressions
28+
*/
29+
public class SynapseExpressionUtils {
30+
31+
/**
32+
* Checks whether the synapse expression is content aware
33+
*
34+
* @param synapseExpression synapse expression string
35+
* @return true if the synapse expression is content aware, false otherwise
36+
*/
37+
public static boolean isSynapseExpressionContentAware(String synapseExpression) {
38+
39+
// TODO : Need to improve the content aware detection logic
40+
if (synapseExpression.equals("payload") || synapseExpression.equals("$")
41+
|| synapseExpression.contains("payload.") || synapseExpression.contains("$.")) {
42+
return true;
43+
} else if (synapseExpression.contains("xpath(")) {
44+
// TODO change the regex to support xpath + variable syntax
45+
Pattern pattern = Pattern.compile("xpath\\(['\"](.*?)['\"]\\s*(,\\s*['\"](.*?)['\"])?\\)?");
46+
Matcher matcher = pattern.matcher(synapseExpression);
47+
// Find all matches
48+
while (matcher.find()) {
49+
if (matcher.group(2) != null) {
50+
// evaluating xpath on a variable so not content aware
51+
continue;
52+
}
53+
String xpath = matcher.group(1);
54+
try {
55+
SynapseXPath synapseXPath = new SynapseXPath(xpath);
56+
if (synapseXPath.isContentAware()) {
57+
return true;
58+
}
59+
} catch (JaxenException e) {
60+
// Ignore the exception and continue
61+
}
62+
}
63+
}
64+
return false;
65+
}
66+
}

0 commit comments

Comments
 (0)