I want to build a wrapper around log4j2 to do the below:
1) There are around 6 mandatory fields like event_name, action, desc etc
2) Some fields, i want to make them use only certain values, like enum
3) log should be created in key value pairs for Splunk.
Below is my approach:
1) Created a class called CustomLogger accepeting the mandatory fields, logger and variable fields as key value
2) Users can call methods like below:
CustomLogger.info(logger, transactionId, app_name, event_name,
"inside the loop", "inside the loop of the sample app",
CustomLogger.Result.success, "looped in", "loop_count",
String.valueOf(i));
Method definition:
public static String log(LogLevel logLevel, Logger logger,
String transactionId, String app_name, String event_name,
String action, String desc, Result result, String reason,
String... addtnlFields)
Issues with the approach:
1) Not extending the log4j, not sure if this is the right way
2) need to pass the logger from every class. If that can be avoided
3) method and line number is lost since it is getting called from a different method
This will be widely used across my internal applications, so want to do it right. Is this approach ok or is there a better approach?
Take a look at the code generator attached to this Jira: https://issues.apache.org/jira/browse/LOG4J2-519
Perhaps you can use that as a base class? Gives you a slightly nicer API.
(I still need to update this to reflect some API changes in log4j-2.0-rc2...)
UPDATE
A different approach is to have a custom implementation of the Message interface defined in the log4j2 api module. Your custom message would have a constructor with all fields you define as required, and the toString method (and perhaps some other methods too) would format these fields as you require into key-value pairs.
Related
I have a Modelica simulation model composed by some models connected to each other.
I would like to save some data of some of the model instances in my simulation model at a given time using the built-in function
Modelica.Utilities.Streams.writeRealMatrix();
To be sure which instance writes which file, I would like to include the instance name in the writeRealMatrix() output file name, e.g., in case I have an instance called myModel, using the name:
myModelOut.mat.
To do this, I need a way to get the instance name and put it into a string.
I know that Modelica allows using instance names in model icons, through a Text record, using the keyword "%name", but I don't know how to do the same in a regular string (I mean outside any record or icon annotation).
Does anyone know if there is a way to do this?
Thank you in advance.
In your case I think the function getInstanceName() should be a good approach. Using it will need you to edit the model, but given you are writing information from with the class using writeRealMatrix() this shouldn't be an issue.
I have created a small example package with a constant block, that stores its name into final parameter of type String. The example then writes the string to the console at the termination of the simulation:
package GetName
block ConstantNamed "Generate constant signal of type Real"
extends Modelica.Blocks.Sources.Constant;
final parameter String name = getInstanceName();
end ConstantNamed;
model Example
extends Modelica.Icons.Example;
ConstantNamed myConst(k=23) annotation (Placement(transformation(extent={{-10,-10},{10,10}})));
equation
when terminal() then
Modelica.Utilities.Streams.print("### Here is the models full path: '" + myConst.name + "'");
end when;
end Example;
annotation (uses(Modelica(version="4.0.0")));
end GetName;
This should result in a simulation log containing the path of the instance of ConstantNamed, which is Example.myConst:
Note: The print function is added to Example in the above code. It could be added to the ConstantNamed as well. For the case from the question, the print shouldn't be necessary anyways...
Besides that, in case you are using Dymola, there is the ModelManagement library, which contains some functions like ModelManagement.Structure.AST.Classes.ComponentsInClass. But these are more intended to be applied from "outside" to a given model.
I have a number of logs like this:
Log.Information("Submitting order {#order}", order);
This log goes through RabbitMq -> LogStash -> Elastic and ends up generating a lot of fields (I assume one field for each propery). Eventually I have thousands and thousands of fields in Elastic which brings all kinds of problems.
If I specify the whole object as a parameter, it usually means I don't care much about having all its fields being parsed, I would be more than happy if it was stored as a single string object (but still serlialised as json). Is there a way to customise it in Serilog?
#flaxel's answer works well if you're happy to change the ToString() representation of your object. If you have already overriden ToString() or you don't want it to return a JSON string then consider one of the following options.
If you don't want to log the type as JSON all the time, consider just serializing the object when you log the message. This is the most explicit approach, and allows you to pick and choose which messages have the serialized form and which have the destructured form, but it might make your log statements quite verbose:
// Using Newtonsoft.Json to serialize.
Log.Information("Submitting order {Order}", JsonConvert.SerializeObject(order));
If you always want to serialize a type to JSON, you could register a destructuring policy for that specific type. This keeps your log statements concise and ensures that type is always serialized in the same way:
// When configuring your logger.
Log.Logger = new LoggerConfiguration()
.Destructure.ByTransforming<Order>(order => JsonConvert.SerializeObject(order))
// ...
// This will use the destructurer registered above, so will convert to a JSON string.
Log.Information("Submitting order {#Order}", order);
// These will still use the ToString() method.
Log.Information("Submitting order {Order}", order);
Log.Information("Submitting order {$Order}", order);
Another advantage of this approach is that if you want to change the way you're representing objects of that type, or if you want to revert to the default destructuring approach, you just have to change the policy used when configuring the logger (i.e. the lambda in the snippet above).
If your serialization approach is too complicated to fit in a lambda, or you want to use the same serialization approach for a large number of types, you could define your own IDestructuringPolicy and then register it in a similar way:
class SerializationPolicy : IDestructuringPolicy
{
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
{
// Check type of `value` and serialize if required.
}
}
// When configuring your logger.
Log.Logger = new LoggerConfiguration()
.Destructure.With<SerializationPolicy>()
// ...
I think you can force stringification with the $ operator. And I think you can then override the ToString method to adjust the output. There is also a short example in the documentation of serilog how to force stringification:
var unknown = new[] { 1, 2, 3 }
Log.Information("Received {$Data}", unknown);
And this is the output of the logging function:
Received "System.Int32[]"
I have a groovy domain class. Fields are not mapped to database.
Controller creates a list of such object and sort it before send to the view.
instances = instances.sort() { it.part_number }
It didn't sort. And print members of the list returns 0 for integer field, null for String field.
Printing the first element
println instances[0]
resulted in
mypackage.Order : (unsaved)
Why it is unsaved? And not sortable?
It would work if I move my domain class to src/groovy as POGO class. But only when I declareed part_number as String. If part_number is Integer. It would crash.
| Error 2015-01-15 09:17:14,771 [http-bio-8080-exec-2] ERROR errors.GrailsExceptionResolver - GroovyCastException occurred when processing request: [GET] myapp/showInstances - parameters:
sort: part_number
order: asc
Cannot cast object '259042' with class 'groovy.util.slurpersupport.NodeChildren' to class 'java.lang.Integer'. Stacktrace follows:
Message: Cannot cast object '259042' with class 'groovy.util.slurpersupport.NodeChildren' to class 'java.lang.Integer'
mypackage.Order : (unsaved) is the result of the implementation of toString() that Grails adds to domain classes - it's the full class name with package and the id, or the string (unsaved) if it's not a persistent instance. But other than telling you the class name and that there's no id, it doesn't indicate anything about the contents of other fields and properties.
The list is very likely sorted, but you're not displaying relevant data from that instance. Try this instead:
println instances[0].dump()
or more directly
println instances[0].part_number
Also consider implementing a toString() method that displays class data, e.g.
String toString() {
part_number
}
Note that Grails domain classes abuse the "domain" concept a bit and are always persistent. If you have a POGO class that you want to use as part of your application domain (in the general sense) those should be in src/groovy (or POJOs in src/java). When you change to that, there won't be any Grails-added toString() method so you'll get the default implementation that all classes get (e.g. something like mypackage.Order#48c02bce, the class name with package and hashCode value in hex form)
EDIT - update based on updated question
Your XML parsing code is bad. You're storing a groovy.util.slurpersupport.NodeChildren instance which has a toString() result of "259042", but that's neither an Integer nor a String - just an object with data that can be converted to both. The reason it works when you define the property type as String is that anything can be stored by Groovy in a String property - it just calls toString() on it. But there's no support in Groovy for automagically converting a NodeChildren instance that happens to have a stringified number to that number - you need to do that yourself. That's independent of the sorting issue though, so that would need to be discussed in a separate question.
In my jsp page I have a form (in struts2) in which I try to assign the size of the ArrayList data to the Vector testVector using hidden tag after submitting the form, but I still get the size of testVector equal to 0.
<s:hidden name="testVector.size" value="%{data.size}" />
I have created setters and getters for data and testVector in my Action class.
private Vector<String> testVector = new Vector<String>();
private ArrayList<String> data = new ArrayList<String>();
private String testName; // a field for testing the form.
// getters and setters of testVector and data
I supposed that this should work because Vector has a public setter setSize(int size).For the other field "testName" it was well submitted.. So am I have a problem in syntax?
Thank you a lot.
You should not be using a Vector. Be aware that OGNL does not work the same from a requests as it does when rendering a JSP for security reasons, it is simply too powerful. You should just set bean properties, collections, arrays and built in types from the request (type converters ignored).
From the request in this case it thinks are are trying to put values into the vector. I think it will create a new string put it into the Vector and then try to set the size of string (which will not work because that method does not exist).
To confirm this I tried to do what you are saying and the log states:
WARNING: Error setting expression 'testVector.size' with value '[Ljava.lang.String;#5c7b2d2f'
ognl.OgnlException: Error converting given String values for Collection. [ognl.NoSuchPropertyException: java.lang.String.size]
If you want this behaviour you will need to create a second method such as setSize() within your action which then goes about modifying the vector size. This I don't think is a very good thing to expose to the outside world, what if someone called your action many times passing in a huge number?
I am working in a grails application, I have many domains in the applications, more than 50! The error message generated are default, I have the following in my message.properties file
default.blank.message=Field "{0}" cannot be blank.
An example of my one of the domain class is as follows:
class Person {
String firstName;
String lastName;
String middleName;
Date dob;
String gender;
String religion;
String nationality;
String maritalStatus;
Zone permAddZone;
District permAddDistrict;
String permAddVDC;
}
And so on, so when I leave a field, for example 'maritalStatus' the following error message shows up, 'Field "maritialStatus" cannot be blank.'. I also added the following to my message.properties file
person.maritialStatus=Maritial Status
But still I am getting the 'Field "maritialStatus" cannot be blank.' message. I also tried using I18n Templates Plugin, but as I have a lot of domain classes, modifying error message for all of them would be time consuming. So, what choices do I have or what am I doing wrong. Any help is appreciated.
The appropriate message key to override is specified on the reference page for each constraint type. In the case of blank it's
com.example.Person.maritalStatus.blank=Marital status must not be blank
However this will be very time consuming and repetitive if you have many domains and many properties.
What you are asking for is instead a way to process the domain class property names in some way before they are inserted into the default messages. This isn't something Grails supports out of the box but I like a challenge... and I've found a surprisingly elegant way to implement it. My approach is the grails plugin available at https://github.com/ianroberts/recursive-messages and it works by extending the format string syntax to support placeholders of the form
default.blank.message=Field "{0,message,field.name.}" cannot be blank.
A placeholder {N,message,prefix} is resolved by prepending the given prefix to the usual placeholder replacement value and then treating the resulting string (field.name.maritalStatus in this example) as a no-argument message key, and looking that up in the usual way. Thus you could have different representations for different languages.
It has to be a plugin because it relies on a trick that works in a plugin's doWithSpring but doesn't work in an application's resources.groovy, to modify the definition of the default messageSource Spring bean.
Disclaimer: this was a quick fix, it hasn't been fully tested and could probably be implemented more efficiently.
In order to override the field label you need to override the key:
<package>.<domainclass>.<fieldname>.label
So in your case, try:
<package>.Person.martialStatus.label=Label