I'm using structured logging in a Spring Boot app using logstash and sometimes I want to include key values in the log that I don't want to be used in the message of the log. Is there a StructuredArgument or similar that allows for this?
An example of what I am doing currently is something like this:
// import lombok.extern.slf4j.Slf4j;
// import static net.logstash.logback.argument.StructuredArguments.kv;
// import static net.logstash.logback.argument.StructuredArguments.v;
log.info(
"My message with one arg {}",
v("key1":"arg I want to include value in place of placeholder in message and in json as key/value"),
kv("key2", "arg I only want in the json and not in the message"))
Everything works as I intended, by which I mean the log includes both key value pairs and the message only includes the first value in place of the placeholder. The issue is that I get a warning from the compiler which is flagged by intellij (PlaceholderCountMatchesArgumentCount) about the second structured argument and I would like to avoid this without resorting to suppressing/ignoring it
You can use Markers and pass it before your logging message - more details on github.
logger.info(append("key2", "only json"),
"My message with one arg {}",
v("key1":"arg in msg and json"));
I personally don't like this because markers have different purpose, so if structured argument works for you, just ignore warning in IDE.
Anyway, all this json/structured implementations are workarounds for SLF4J 1.*, which has not built for that. There was SLF4J 2.0.0-alpha1 release almost a yeah ago, but it is still in alpha and I haven't used it. But it's API should be ready for key-values that are crusial in nowadays distributed log management systems.
You can make the log message as a constant String, then the code quality checks will not warn this
You can make the structured argument print nothing into the formatted message:
(1) include the second placeholder {} inside the message
(2) use keyValue() instead of kv()
(3) provide the optional messageFormatPattern parameter (JavaDoc) equal to ""
Adjusting your example:
log.info(
"My message with one arg {}{}", //note (1)
v("key1":"arg I want to include value in place of placeholder in message and in json as key/value"),
keyValue("key2", "arg I only want in the json and not in the message", "")) //note (2) + (3)
This will effectively replace the second placeholder with an empty string.
Related
I'm following the examples here -> https://github.com/apache/camel-k-examples. Working on 05-knative-source-jira
When running this integration, I'm able to read and log new jira issues just fine, I fall down when I try to use info from the ticket, or respond to the ticket with the jira addComment producer.
I've tried just putting a static ticket number in for the IssueKey option, but I get build errors and can't even get the producer to run.
I've tried tinkering with the URI...
Ex: Changing URI to -> .to("jira://addComment?IssueKey=EQ-7") returns below on build
No signature of method: org.apache.camel.builder.ValueBuilder.to() is applicable for argument types: (String) values: [jira://addComment&IssueKey=EQ-7]
I've tried this with both ? and &, as well as adding properties to the URI with similar results.
I feel like I'm missing something pretty fundamental, so any docs pointers would be well appreciated.
Full integration here
// camel-k: language=groovy
from('knative:channel/jira')
.unmarshal()
.json()
.log('Recieved: ${body}')
.to('direct:ticket')
from("direct:ticket")
.setBody().simple("testing")
.to("jira://addComment?IssueKey=EQ-7")
I ended up sorting through enough docs to find the answer. I'll share details just for others who might find this (or if I google it again).
The key was to
a) Set the required headers for the issue key. Seting headers examples
b) Ensure that my properties are set correctly. I used a configmap to set my properties, and then referenced them as shown below in the URI. I believe this should also be possible through DSL but URI was easiest for me to just get working.
Functional Integration below.
from("direct:ticket")
.setHeader("IssueKey").simple('${body["key"]}')
.setBody().simple("We've recieved the ticket -- we'll update you soon!")
.to("jira://addComment?jiraUrl={{url}}&consumerKey={{consumer_key}}&accessToken={{access_token}}&privateKey={{private_key}}&verificationCode={{verification_code}}")
I mean for example this is my messages.properties file:
BFF.ERROR.PRODUCT_NOT_FOUND = Product with {0} not found
I want to do that if the arguments array is empty, the client shouldn't see the message like this
Product with {0} not found
I want to the user see this one
Product not found.
Can I do something like that?
BFF.ERROR.PRODUCT_NOT_FOUND = Product with {0} not found | Product not found
I think u can define two keys :
BFF.ERROR.PRODUCT_NOT_FOUND = Product with {0} not found
BFF.NULL.PRODUCT_NOT_FOUND = Product not found
then deal with it in code
Why would the arguments array be empty? Can you not use two different keys and check ahead of time to use the correct message based on the arguments? They're two different messages and should have their own keys.
messages.properties
key1=message with argument {0}
key2=message without argument
Somewhere in your code
final String msg;
if (condition) {
msg = ... // if you have the argument for placeholder {0}, get message for key1
} else {
msg = ... // fallback, get message for key2
}
If you really wanted to you could look into creating a custom message source and/or supporting beans and handle this kind of thing there but it seems like more trouble than it's worth. In that case, have a look at the MessageSourceSupport class Spring provides, in particular methods formatMessage and renderDefaultMessage.
I would like to "improve" some exception messages thrown by Freemarker template messages to make the exceptions more meaningful for the users. Although Freemarker has become a lot better in terms of meaningful error messages, there are still cases, where I would like to be more specific.
Example
Freemarker is throwing this exception for a template like this:
<#if (""?number > 1)>foo</#if>
(just an example... imagine the empty string could also be a variable containing an empty string)
value of templateException.getMessage():
(java.lang.String) Can't convert this string to number: ""
The blamed expression:
==> ""?number [in nameless template at line 1, column 7]
----
FTL stack trace ("~" means nesting-related):
- Failed at: #if (""?number > 1) [in nameless template at line 1, column 1]
----
I would like to rephrase this specific case to:
You tried to convert an EMPTY string variable to a number.
I could try my own Exception handler, to contains checks, replace the message and rethrow an Exception like this:
configuration.setTemplateExceptionHandler(new TemplateExceptionHandler() {
public void handleTemplateException(TemplateException te, Environment env, java.io.Writer out)
throws TemplateException {
String message = te.getMessage();
if(StringUtils.contains(message, "Can't convert this string to number: \"\"")){
message = StringUtils.replace(message, "Can't convert this string to number: \"\"", "You tried to convert an EMPTY string variable to a number. Solution: Try checking if the variable is empty to avoid this error.");
}
throw new TemplateException(message, env);
}
});
But this feels very hacky.
My questions:
Is there a way how I can customize the Exception messages Freemarker is throwing? I have the feeling in my TemplateExceptionHandler it is too late, as the message gets constructed much earlier inside Freemarker.
What are common ways to improve / rewrite exception messages from 3rd party libs?
Search and replace may won't work after version updates, as there's no backward compatibility promise regarding the message content.
If the changes you want are generally useful (not only for your project), then you could improve the existing error messages by contributing to FreeMarker (sign Apache CLA, fork on GitHub, make pull request).
The only really correct and flexible way I see is adding l10n support to the error message mechanism, where the message strings aren't hard-wired in to the code (except their defaults), but are retrieved based on message keys from external source. It can be a big work of course, especially as FreeMarker messages are assembled from many smaller pieces.
I have an application that has two classes called "Service" in two different places in the package structure. The logging outputs the file and line number like this, eg: (Service.java:102)
This becomes a clickable link in the console output in Eclipse. Normally, these links are great because you can find exactly of where the output was printed from with a single click.
But now I have two Service.java files, doing two entirely different things, and they're in a different place in the package structure. I can't rename either of them.
When I click on the link, it takes me to the wrong java file, even when the correct java file is open in the editor.
I've searched around, but I can't find the answer. Is there a way to tell Eclipse which java file to consider first? Or a way to tell which package to look in first? Something, anything, to make these clickable links useful again?
I guess your logger is configured like this to ouput a log like that (Service.java:102) :
(%F:%L)
%F : Used to output the file name where the logging request was issued.
%L : Used to output the line number from where the logging request was issued.
Try to used %l instead
%l : Used to output location information of the caller which generated the logging event.
EDIT
this solution does not seem to work well, it prints
com.x.y.z.MyClass.myMethod(MyClass.java:36)
=> the link is only on the classname, same issue.
But using the following pattern will work
(%C.java:%L)
It will print a full link like this :
(com.x.y.z.MyClass.java:36)
i know three ways how to print a clickable class link in the console output.
First way:
Just print the class name inside the parentheses: System.out.println("(Service.java:42)");
This is simple method and will work if you are not using ambiguous class names. Since Eclipse console does not have informations required to decide which file should be opened, i guess it will open the first occurence.
Second way:
In your case. I would do it by using StackTraceElement to print the class name.
That way:
StackTraceElement element = new StackTraceElement(
"Service", // Class name
"myFunnyMethodName", // Method name
Service.class.getName()+".java", // Path to File
2); // line number
System.out.println(element);
Third way:
If you don't want to create StackTraceElement you can get it from you current thread.
Example:
System.out.println(Thread.currentThread().getStackTrace()[1]);
EDIT:
The other option is to try the Grep Console Plugin for Eclipse. You can define your own Expressions with Link, Color, Popup etc...
I trying to create queues using PCF command in the WebSphere API as detailed in $MQM_HOME/samp/pcf/samples/PCF_CreateQeue.java. The creation fails when i add a description
command.addParameter(PCFConstants.MQCA_Q_DESC, "Created using MQMonitor");
I get the error: com.ibm.mq.pcf.PCFException: MQJE001: Completion Code 2, Reason 3015 : MQRCCF_CFST_PARM_ID_ERROR
Is there another way of setting the description, i'm using version 6 of the API.
The Commands page in the PCF manual states that:
The required parameters and the
optional parameters are listed. On
platforms other than z/OSĀ®, the
parameters must occur in the order:
All required parameters, in the order stated, followed by
Optional parameters as required, in any order, unless specifically
noted in the PCF definition.
The section Change, Copy and Create Queue lists the required parameters in the following order:
MQCA_Q_NAME
MQIA_Q_TYPE
Optional parameters, including QDesc
The same manual provides required parameters and their order for all PCF commands so no need to play hide-and-seek trying out parms and orders in the future.
It turns out the addParameter on the PCFMessage should in a certain sequence (stumbled on it). If i change the add parameters if works. This is not just for creating queues, but for channels as well.
command.addParameter(PCFConstants.MQCA_Q_NAME, qname);
command.addParameter(PCFConstants.MQIA_Q_TYPE, PCFConstants.MQQT_LOCAL);
command.addParameter(PCFConstants.MQCA_Q_DESC, qdesc);
command.addParameter(PCFConstants.MQIA_DEF_PERSISTENCE, PCFConstants.MQPER_PERSISTENT);
the above will execute without error.
command.addParameter(PCFConstants.MQCA_Q_NAME, qname);
command.addParameter(PCFConstants.MQCA_Q_DESC, qdesc);
command.addParameter(PCFConstants.MQIA_Q_TYPE, PCFConstants.MQQT_LOCAL);
command.addParameter(PCFConstants.MQIA_DEF_PERSISTENCE, PCFConstants.MQPER_PERSISTENT);
the above will fail after moving around the description.
I haven't seen it documented in the Java docs, and if thats the case i looks forward to some hide and seek.