Logging: Associate logs with object being created - java

I have Spring Boot project with a MainService that uses several helper methods and other "microservices" to create a FinalObject, that's eventually persisted using Hibernate/JPA. The methods and services may log several messages, and I want these to be associated with the object that was being created when the logged event occurred.
The problem is that the helper methods and microservices don't have access to the finalObject instance, so even though everything is logged, only caught exceptions get saved as a finalObject attribute - not warning messages or other logs:
class FinalObject {
private int value;
private int price;
private List<String> logs;
...
}
class MainService {
#Autowired ValueService valueService; // + other services
void createFinalObject() { // Main method
FinalObject o1 = new FinalObject();
try {
o1.setValue(valueService.getValue("some argument"));
}
catch (Exception e) {
log.error(e.toString()); // Logging using Log4j2
o1.addLog(e.toString()); // If there's an exception, I can easily log it to a o1 attribute.
}
o1.setPrice(calculatePrice(o1.getValue()));
...
}
int calculatePrice(int value) { // Helper method
if (value > getMarketPrice())
log.info("This is very valuable!"); // I need a way to associate this with o1!
...
return price;
}
}
// ValueService.java
int getValue(String arg) {
if (arg.matches("\\d$"))
log.warn("arg ends with a number"); // Must also be saved to o1!
...
return value;
}
Solution 1: Passing o1 around everywhere:
int calculatePrice(int value, FinalObject o1) {
if (value > getMarketPrice()) {
o1.addLog("This is very valuable!"); // Now I have access to o1 here
log.info("This is very valuable!");
}
...
Solution 2: Pass o1's logs attribute around as a modifiable list:
o1.setPrice(calculatePrice(o1.getValue(), o1.getLogs()));
...
int calculatePrice(int value, List<String> finalObjectLogs) {
if (value > getMarketPrice()) {
finalObjectLogs.add("This is very valuable!"); // Directly modify o1's logs attribute
log.info("This is very valuable!");
}
...
Solution 3: Add a log4j2 database appender
A more elegant solution may be do add a database appender to log4j2. A challenge with this is how I can relate these logs to o1. The FinalObject id only gets generated at the very end of createFinalObject(), when it is saved to the database, so I don't have an id when the log statements are executed.
Question:
How can I do this more elegantly than the ways I mentioned above?
Or:
If solution 3 seems like a good approach, how do I implement it?

I am curious. In your example the method calculate price doesn't seem to relate to any object so why would you want to include information about a particular object in it?
On the other hand, if you want to correlate it with other operations being performed in a single Request I would suggest you look at the RequestContext that Log4j-Audit describes. You don't have to use Log4j Audit to implement something like this. It simply leverages the Log4j ThreadContext and defines specific keys that are used. You then initialized the values in the ThreadContext and the beginning of the request and clear it at the end of the Request. You can add items to the ThreadContext as needed. Log4j can then be configured to include specific keys in every log event.
I should note that Spring Cloud Sleuth is doing something that I have been doing for years. To propagate the Request information from one service to the next they simply need to be converted to and from HTTP headers when calling a service and when the service starts. That is why the Log4j-Audit example RequestContext shows annotations to classify the attributes as ClientServer, Local, or Chained. It then provides the components needed to accomplish this.

You can use spring sleuth and logging filters to log the endpoints.
Check the Spring Sleuth Docs;
https://docs.spring.io/spring-cloud-sleuth/docs/2.2.4.RELEASE/reference/html/
You can create a filter do log every endpoint, or log one of them.
And with this loggers you can log the types that you want.

Related

Ways to pass additional data to Custom RevisionEntity in Hibernate Envers?

It's RESTful web app. I am using Hibernate Envers to store historical data. Along with revision number and timestamp, I also need to store other details (for example: IP address and authenticated user). Envers provides multiple ways to have a custom revision entity which is awesome. I am facing problem in setting the custom data on the revision entity.
#RevisionEntity( MyCustomRevisionListener.class )
public class MyCustomRevisionEntity extends DefaultRevisionEntity {
private String userName;
private String ip;
//Accessors
}
public class MyCustomRevisionListener implements RevisionListener {
public void newRevision( Object revisionEntity ) {
MyCustomRevisionEntity customRevisionEntity = ( MyCustomRevisionEntity ) revisionEntity;
//Here I need userName and Ip address passed as arguments somehow, so that I can set them on the revision entity.
}
}
Since newRevision() method does not allow any additional arguments, I can not pass my custom data (like username and ip) to it. How can I do that?
Envers also provides another approach as:
An alternative method to using the org.hibernate.envers.RevisionListener is to instead call the getCurrentRevision( Class revisionEntityClass, boolean persist ) method of the org.hibernate.envers.AuditReader interface to obtain the current revision, and fill it with desired information.
So using the above approach, I'll have to do something like this:
Change my current dao method like:
public void persist(SomeEntity entity) {
...
entityManager.persist(entity);
...
}
to
public void persist(SomeEntity entity, String userName, String ip) {
...
//Do the intended work
entityManager.persist(entity);
//Do the additional work
AuditReader reader = AuditReaderFactory.get(entityManager)
MyCustomRevisionEntity revision = reader.getCurrentRevision(MyCustomRevisionEntity, false);
revision.setUserName(userName);
revision.setIp(ip);
}
I don't feel very comfortable with this approach as keeping audit data seems a cross cutting concern to me. And I obtain the userName and Ip and other data through HTTP request object. So all that data will need to flow down right from entry point of application (controller) to the lowest layer (dao layer).
Is there any other way in which I can achieve this? I am using Spring.
I am imagining something like Spring keeping information about the 'stack' to which a particular method invocation belongs. So that when newRevision() in invoked, I know which particular invocation at the entry point lead to this invocation. And also, I can somehow obtain the arguments passed to first method of the call stack.
One good way to do this would be to leverage a ThreadLocal variable.
As an example, Spring Security has a filter that initializes a thread local variable stored in SecurityContextHolder and then you can access this data from that specific thread simply by doing something like:
SecurityContext ctx = SecurityContextHolder.getSecurityContext();
Authorization authorization = ctx.getAuthorization();
So imagine an additional interceptor that your web framework calls that either adds additional information to the spring security context, perhaps in a custom user details object if using spring security or create your own holder & context object to hold the information the listener needs.
Then it becomes a simple:
public class MyRevisionEntityListener implements RevisionListener {
#Override
public void newRevision(Object revisionEntity) {
// If you use spring security, you could use SpringSecurityContextHolder.
final UserContext userContext = UserContextHolder.getUserContext();
MyRevisionEntity mre = MyRevisionEntity.class.cast( revisionEntity );
mre.setIpAddress( userContext.getIpAddress() );
mre.setUserName( userContext.getUserName() );
}
}
This feels like the cleanest approach to me.
It is worth noting that the other API getCurrentRevision(Session,boolean) was deprecated as of Hibernate 5.2 and is scheduled for removal in 6.0. While an alternative means may be introduced, the intended way to perform this type of logic is using a RevisionListener.

In Java - Can I set log level pr thread?

I always wondered if it would be possible to programatically set the log level of java.util.logging on a pr thread level.
The usecase is, in a web server. If my code detects that a certain user has an abnormal level of errors, I would like to enable trace logging for that customer.
I was thinking of doing this with a servletfilter, and tie it to his session id. But as far as I can see, the log levels I can set on appenders are not thread affine. I.e. if I set the root level to FINE, it will affect all threads in the system, and the log fills up with the other 200 simultanious users traffic. Sure, I can then filter the log afterwards, but the performance hit will occur anyway.
Does anyone have experience with this?
I always wondered if it would be possible to programatically set the log level of java.util.logging on a pr thread level.
Create a log filter to capture the user context and set the level to ALL for the filtered object:
public class UserFilter implements Filter {
private final Level lvl;
private final Object userContext;
public UserFilter(Level originalLevel, Object userContext) {
this.lvl = originalLevel;
this.userContext = userContext;
}
#Override
public boolean isLoggable(LogRecord record) {
if (!belongsTo(record, userContext)) {
int levelValue = this.lvl.intValue();
if (record.getLevel().intValue() < levelValue
|| levelValue == Level.OFF.intValue()) {
return false;
}
}
return true;
}
private boolean belongsTo(LogRecord record) {
/**
* Insert logic here.
*/
//return ((Integer) this.userContext) == record.getThreadId();
return true;
}
}
For your use case you should be able to create a servlet filter which will:
Create a log filter which will identify a user and capture the current log level.
Install the log filter then change the level to FINE.
Well, you should always be able to call log(level, message); directly and get the actual level by using some thread local mapping.
You could also write some wrapper which handles this for you, i.e. with something like this:
public void debugExceptCritical(Logger l, String message) {
//critical would be your per-thread flag,
//just make sure to reset it when it is no longer used, e.g. in a servlet filter
l.log( critical ? ERROR : DEBUG, message );
}
Alternatively you could try and get the class where this is called (I did it somewhere but don't remember the details, so I'd have to look it up) and use this info to get the logger inside the method.
Looking up the caller:
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for( StackTraceElement elem : stackTrace ) {
//look for the debugExceptCritical method or maybe just the class it contains
//if found take the class name of the next element to look up the correct logger
}
An alternative to this, depending on which logging framework you actually use, might be to hook some per-thread filter/appender/whatever into it. In Log4J I'd either try to add my own appender which then checks the critical flag and changes the log event's level or always log critical actions and use a per-thread filter which rejects those log events when the critial flag is not set.

spring security permission programatic check

I have in place a spring security ACL system, and it seems to work fine, only that I `m not sure how should I perform a permission check programmatically.
My app is split into 3 layers (View,Service(Business),DAO) and I want to perform the auth in the Service layer. So, for a method that take as an argument a domain object :
#PreAuthorize("hasPermission(#proj,'write'")
public Project updateProject(Project proj) {
.............
}
the problem is solved with annotations.
But for a method that take as an argument an object that does not have an acl on it I have to programmatically check if user has permission.
Let`s say i have an object ProjectWrapper:
public class ProjectWrapper {
private Project project;
private Something something;
// setters and getters here
}
so now my Service method received this type of argument:
public Project updateProject(ProjectWapper projWrapp) {
Project p = projWrapp.getProject();
// before performing any operation on project I need to know if current user has neccessary permissions on this object
// ??? how do i check that ?
}
Do i need to use AclService to perform that ? just like when I need to create/update a permission, or is there an cleaner/nicer possibility ?
The same question for deleteProject(Long id) methods,as first i have to get the object from db to check if the current user has delete permission.
Method security annotations support Spring EL expressions. In case of your wrapper class, you can use it as follows.
#PreAuthorize("hasPermission(#projectWrapper.project, 'write'")
public Project updateProject(ProjectWrapper projectWrapper) {
// body omitted
}
And if you have just an object identifier instead of the actual object, you can use pattern below.
#PreAuthorize("hasPermission(#id, 'my.package.Project' 'delete'")
public void deleteProject(Long id) {
// body omitted
}
You may need to adjust default configuration (e.g. strategy to retrieve object identity and the like) to meet you requirements. See org.springframework.security.acls.AclPermissionEvaluator class for more details.

Logging controller/service using Log4j ( Centralized logging )

I don't want to use something like this :
Logger.getLogger(MyClass.class).info("Info message");
each time I want to log something.
I've tried to implement a logging service that has methods like :
public void info(final Object aMessage, final Object aSender);
This allows me get Logger instance based on sender's class, but it hides the method and line of the log source class.
I know about alternatives like AOP or Slf4j. The first one is not exactly I want, the second one introduces something similar to the first line of code :
LoggerFactory.getLogger(HelloWorld.class).info("Info message");
So, my concern is not about hiding Log4j dependency, it's about unifying logging calls through the whole application, like this :
//loggingController instance was injected previously
loggingControler.info("Info message",this);
Is there any way to implement this ?
Ok, seems that there is at least one way to resolve the issue :
For example there are LoggingService and LoggingController. The first one works directly with Log4j, the second one is just a layer between service and the whole application.
public class LoggingService implements ILoggingService{
//other methods here.
#Override
public void warn(final Object aMessage, final Object aSender, final Throwable aThrowable) {
log(aMessage, aSender, Level.WARN, aThrowable);
}
private void log(final Object aMessage, final Object aSender, final Level aLevel, final Throwable aThrowable) {
final String className = getClassNameBy(aSender);
Logger.getLogger(className).log(className, aLevel, aMessage, aThrowable);
}
}
public class LoggingController implement ILoggingController{
//logging service is injected previously
#Override
public void warn(final Object aMessage, final Throwable aThrowable) {
loggingService.warn(aMessage, this, aThrowable);
}
}
So, in this case you allow the user to log something using :
loggingController.warn("A warning", null);
Using this way:
User knows nothing about underlying logging functionality
You always have the possibility to provide a dummy logger if you don't need it, or the environment doesn't allow it.
The logging functionality is unified across the whole application
The class name and code line are shown correctly in the log.
You cannot use one of the most useful features of the Log4j - filtering by package/class name.

Registering and updating application properties using JMX

I have to access some application through an mbean so that I can change its application properties. Now i think this can be done in two ways:
First, either I ask the developer of that application to register all the application properties in an arraylist which my mbean will access.
Secondly, if there is any other way, such that the developer will only need to register editable properties and still my mbean can access both readable/editable(r/w) application properties.
Now since I don't know where these application properties are stored in the JVM, is there a way to implement my second point so that the mbean will just need to access that object and it will get all application properties?
Seems like you have some contradicting requirements here.
You want to change minimal code in the application.
You want to be cause to expose all properties for read and/or write.
You may or may not be talking about System.getProperty(...). If not then I guess you are talking about just fields in various objects.
There are (at least) two ways of doing this. Without knowing how you are exporting the mbeans from the source code right now, I can't tailor my answer to your specific config. My answer will instead show how you might use my SimpleJMX package to expose your properties.
If you are talking about System.getProperty(...) then you could write a JMX mbean that could query any property and return the value:
#JmxOperation(description = "Query for property")
public String queryForProperty(String name) {
return System.getProperty(name);
}
If, instead, you need to export of fields from some list of objects then you are going to either have to add annotations to each fields you are exporting or you are going to have to write beans that export the fields through delegation. I see no easy way and I know of no package that will easily export a series of objects for you without some sort of information about what is to be exported and how.
Using SimpleJMX, you can export a field by annotating either the field or the get method:
#JmxAttributeField(description = "Number of hits in the cache")
private int hitCount;
...
// this can also be specified as #JmxAttributeMethod on the getter/setter methods
#JmxAttributeMethod(description = "Number of misses in the cache")
private int getMissCount() {
return missCount;
}
The #JmxAttributeField supports a isWritable = true to allow the value to be set by JMX. You can also annotation the setter with #JmxAttributeMethod to make it writable.
If you don't want to add annotations to each of your classes, then you are going to have to write some sort of JMX container class that exports the values through delegation:
public class JmxPublisher {
private Class1 object1;
private Class2 object2;
...
public JmxPublisher(Class1 object1, Class2 object2) {
this.object1 = object1;
this.object2 = object2;
...
}
#JmxAttributeMethod(description = "Number of hits in the cache")
public int getClass1HitCount() {
return object1.getHitCount();
}
#JmxAttributeMethod(description = "Shutdown the background thread")
public void setClass2Shutdown(boolean shutdown) {
return object2.setShutdown(shutdown);
}
...
}
I also think you should express yourself more clearly.
From what I understood - why not providing a way to query the remote application, and get information on all properties and if they are Read-only, Write-only or RW?
This way the list of properties will not be mentioned at the source code of the client application - maybe you should let the user of the client application see the list of properties, and let him edit the properties he can edit, and prevent him from editing the properties he can't.

Categories

Resources