Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve log mediator to support string templating #2230

Merged
merged 4 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why an additional new line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's added by the wso2_code_style for idea.

return messageTemplate;
}

public void setMessageTemplate(String messageTemplate) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the stringValueOf fails we get the value "unknown" should we print a warn log outside the while loop saying processed template might have an incorrect result since evaluation of some expressions failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot distinguish the unknown at the mediator level. We may need to print the warn log at the evaluation.

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
Loading