Skip to content

Commit

Permalink
Merge pull request #2230 from SanojPunchihewa/log-mediator
Browse files Browse the repository at this point in the history
Improve log mediator to support string templating
  • Loading branch information
SanojPunchihewa authored Dec 3, 2024
2 parents b5a55c1 + 2463b2c commit a460488
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
*
* <pre>
* &lt;log [level="simple|headers|full|custom"]&gt;
* &lt;message&gt;String template&lt;/message&gt;
* &lt;property&gt; *
* &lt;/log&gt;
* </pre>
Expand All @@ -52,6 +53,8 @@ public class LogMediatorFactory extends AbstractMediatorFactory {
private static final QName ATT_LEVEL = new QName("level");
private static final QName ATT_SEPERATOR = new QName("separator");
private static final QName ATT_CATEGORY = new QName("category");
protected static final QName ELEMENT_MESSAGE_Q
= new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "message");

public QName getTagQName() {
return LOG_Q;
Expand All @@ -64,7 +67,14 @@ public Mediator createSpecificMediator(OMElement elem, Properties properties) {
// after successfully creating the mediator
// set its common attributes such as tracing etc
processAuditStatus(logMediator,elem);


boolean containMessageTemplate = false;
OMElement messageElement = elem.getFirstChildWithName(ELEMENT_MESSAGE_Q);
if (messageElement != null && messageElement.getText() != null) {
logMediator.setMessageTemplate(messageElement.getText());
containMessageTemplate = true;
}

// Set the high level set of properties to be logged (i.e. log level)
OMAttribute level = elem.getAttribute(ATT_LEVEL);
if (level != null) {
Expand All @@ -81,6 +91,10 @@ public Mediator createSpecificMediator(OMElement elem, Properties properties) {
handleException("Invalid log level. Level has to be one of the following : "
+ "simple, headers, full, custom");
}
} else {
if (containMessageTemplate) {
logMediator.setLogLevel(LogMediator.MESSAGE_TEMPLATE);
}
}

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

logMediator.addAllProperties(MediatorPropertyFactory.getMediatorProperties(elem));

logMediator.processTemplateAndSetContentAware();
addAllCommentChildrenToList(elem, logMediator.getCommentsList());

return logMediator;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@
package org.apache.synapse.config.xml;

import org.apache.axiom.om.OMElement;
import org.apache.commons.lang3.StringUtils;
import org.apache.synapse.Mediator;
import org.apache.synapse.mediators.builtin.LogMediator;

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

if (mediator.getLogLevel() != LogMediator.SIMPLE) {
if (mediator.getLogLevel() != LogMediator.MESSAGE_TEMPLATE) {
log.addAttribute(fac.createOMAttribute(
"level", nullNS,
mediator.getLogLevel() == LogMediator.HEADERS ? "headers" :
Expand Down Expand Up @@ -73,6 +75,12 @@ public OMElement serializeSpecificMediator(Mediator m) {
"separator", nullNS, mediator.getSeparator()));
}

if (StringUtils.isNotBlank(mediator.getMessageTemplate())) {
OMElement onCompleteElem = fac.createOMElement("message", synNS);
onCompleteElem.setText(mediator.getMessageTemplate());
log.addChild(onCompleteElem);
}

super.serializeProperties(log, mediator.getProperties());

serializeComments(log, mediator.getCommentsList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.mediators.AbstractMediator;
import org.apache.synapse.mediators.MediatorProperty;
import org.apache.synapse.util.InlineExpressionUtil;
import org.jaxen.JaxenException;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


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

/** The message template and the additional properties specified to the Log mediator */
public static final int MESSAGE_TEMPLATE = 4;

public static final int CATEGORY_INFO = 0;
public static final int CATEGORY_DEBUG = 1;
public static final int CATEGORY_TRACE = 2;
Expand All @@ -74,6 +78,9 @@ public class LogMediator extends AbstractMediator {
/** The holder for the custom properties */
private final List<MediatorProperty> properties = new ArrayList<MediatorProperty>();

private String messageTemplate = "";
private boolean isContentAware = false;

/**
* Logs the current message according to the supplied semantics
*
Expand Down Expand Up @@ -136,6 +143,7 @@ public boolean mediate(MessageContext synCtx) {
private String getLogMessage(MessageContext synCtx) {
switch (logLevel) {
case CUSTOM:
case MESSAGE_TEMPLATE:
return getCustomLogMessage(synCtx);
case SIMPLE:
return getSimpleLogMessage(synCtx);
Expand All @@ -150,12 +158,18 @@ private String getLogMessage(MessageContext synCtx) {

private String getCustomLogMessage(MessageContext synCtx) {
StringBuffer sb = new StringBuffer();
processMessageTemplate(sb, synCtx, messageTemplate);
setCustomProperties(sb, synCtx);
return trimLeadingSeparator(sb);
}

private String getSimpleLogMessage(MessageContext synCtx) {
StringBuffer sb = new StringBuffer();
processMessageTemplate(sb, synCtx, messageTemplate);
// append separator if the message template is not empty
if (sb.length() > 0) {
sb.append(separator);
}
if (synCtx.getTo() != null)
sb.append("To: ").append(synCtx.getTo().getAddress());
else
Expand All @@ -180,6 +194,7 @@ private String getSimpleLogMessage(MessageContext synCtx) {

private String getHeadersLogMessage(MessageContext synCtx) {
StringBuffer sb = new StringBuffer();
processMessageTemplate(sb, synCtx, messageTemplate);
if (synCtx.getEnvelope() != null) {
SOAPHeader header = synCtx.getEnvelope().getHeader();
if (getCorrelationId(synCtx) != null)
Expand Down Expand Up @@ -288,6 +303,16 @@ public void setCategory(int category) {
}
}

public String getMessageTemplate() {

return messageTemplate;
}

public void setMessageTemplate(String messageTemplate) {

this.messageTemplate = messageTemplate.replace("\\n", "\n").replace("\\t", "\t");
}

private String trimLeadingSeparator(StringBuffer sb) {
String retStr = sb.toString();
if (retStr.startsWith(separator)) {
Expand All @@ -297,16 +322,32 @@ private String trimLeadingSeparator(StringBuffer sb) {
}
}

private void processMessageTemplate(StringBuffer stringBuffer, MessageContext synCtx, String template) {
try {
stringBuffer.append(InlineExpressionUtil.processInLineSynapseExpressionTemplate(synCtx, template));
} catch (JaxenException e) {
handleException("Failed to process the message template : " + template, e, synCtx);
}
}

@Override
public boolean isContentAware() {
if (logLevel == CUSTOM) {

return isContentAware;
}

public void processTemplateAndSetContentAware() {

if (logLevel == MESSAGE_TEMPLATE || logLevel == CUSTOM) {
for (MediatorProperty property : properties) {
if (property.getExpression() != null && property.getExpression().isContentAware()) {
return true;
isContentAware = true;
return;
}
}
return false;
isContentAware = InlineExpressionUtil.isInlineSynapseExpressionsContentAware(messageTemplate);
} else {
isContentAware = true;
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
Expand All @@ -30,6 +29,8 @@
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.config.xml.SynapsePath;
import org.apache.synapse.util.xpath.SynapseExpression;
import org.apache.synapse.util.xpath.SynapseExpressionUtils;
import org.apache.synapse.util.xpath.SynapseJsonPath;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.jaxen.JaxenException;
Expand All @@ -49,6 +50,9 @@ public final class InlineExpressionUtil {
// Regex to identify expressions in inline text
private static final Pattern EXPRESSION_PATTERN = Pattern.compile("(\\{[^\\s\",<>}\\]]+})");

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

private InlineExpressionUtil() {

}
Expand Down Expand Up @@ -197,4 +201,48 @@ private static boolean isValidXML(String stringToValidate) {
}
return false;
}

/**
* Checks whether inline template contains content aware synapse expressions.
* Inline expressions will be denoted inside ${}
* e.g.: ${var.var1}, ${payload.element.id}
*
* @param inlineText Inline text string
* @return true if the string contains content aware inline synapse expressions, false otherwise
*/
public static boolean isInlineSynapseExpressionsContentAware(String inlineText) {

Matcher matcher = SYNAPSE_EXPRESSION_PLACEHOLDER_PATTERN.matcher(inlineText);
while (matcher.find()) {
// Extract the expression inside ${...}
String expression = matcher.group(1);
if (SynapseExpressionUtils.isSynapseExpressionContentAware(expression)) {
return true;
}
}
return false;
}

/**
* Process the inline template and replace the synapse expressions with the resolved values
*
* @param synCtx Message Context
* @param template Inline template
* @return Processed inline template
*/
public static String processInLineSynapseExpressionTemplate(MessageContext synCtx, String template)
throws JaxenException {

Matcher matcher = SYNAPSE_EXPRESSION_PLACEHOLDER_PATTERN.matcher(template);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
// Extract the expression inside ${...}
String placeholder = matcher.group(1);
SynapseExpression expression = new SynapseExpression(placeholder);
String replacement = expression.stringValueOf(synCtx);
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(result);
return result.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,33 +74,7 @@ public SynapseExpression(String synapseExpression) throws JaxenException {
}
throw new JaxenException(errorMessage.toString());
}

// TODO : Need to improve the content aware detection logic
if (synapseExpression.equals("payload") || synapseExpression.equals("$")
|| synapseExpression.contains("payload.") || synapseExpression.contains("$.")) {
isContentAware = true;
} else if (synapseExpression.contains("xpath(")) {
// TODO change the regex to support xpath + variable syntax
Pattern pattern = Pattern.compile("xpath\\(['\"](.*?)['\"]\\s*(,\\s*['\"](.*?)['\"])?\\)?");
Matcher matcher = pattern.matcher(synapseExpression);
// Find all matches
while (matcher.find()) {
if (matcher.group(2) != null) {
// evaluating xpath on a variable so not content aware
continue;
}
String xpath = matcher.group(1);
try {
SynapseXPath synapseXPath = new SynapseXPath(xpath);
if (synapseXPath.isContentAware()) {
isContentAware = true;
break;
}
} catch (JaxenException e) {
// Ignore the exception and continue
}
}
}
isContentAware = SynapseExpressionUtils.isSynapseExpressionContentAware(synapseExpression);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.synapse.util.xpath;

import org.jaxen.JaxenException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Utility class for Synapse Expressions
*/
public class SynapseExpressionUtils {

/**
* Checks whether the synapse expression is content aware
*
* @param synapseExpression synapse expression string
* @return true if the synapse expression is content aware, false otherwise
*/
public static boolean isSynapseExpressionContentAware(String synapseExpression) {

// TODO : Need to improve the content aware detection logic
if (synapseExpression.equals("payload") || synapseExpression.equals("$")
|| synapseExpression.contains("payload.") || synapseExpression.contains("$.")) {
return true;
} else if (synapseExpression.contains("xpath(")) {
// TODO change the regex to support xpath + variable syntax
Pattern pattern = Pattern.compile("xpath\\(['\"](.*?)['\"]\\s*(,\\s*['\"](.*?)['\"])?\\)?");
Matcher matcher = pattern.matcher(synapseExpression);
// Find all matches
while (matcher.find()) {
if (matcher.group(2) != null) {
// evaluating xpath on a variable so not content aware
continue;
}
String xpath = matcher.group(1);
try {
SynapseXPath synapseXPath = new SynapseXPath(xpath);
if (synapseXPath.isContentAware()) {
return true;
}
} catch (JaxenException e) {
// Ignore the exception and continue
}
}
}
return false;
}
}
Loading

0 comments on commit a460488

Please sign in to comment.