Java Logging – How to Log an Audit Trail in a Java Application

Architecturejavalogging

I'm creating a Java (Spring Boot) application for creating Entities. I've got my @Controller for getting the data from a web front end, I've got my @Service for processing the data, and I've got my @Repository for permanently storing the Entities. So, your basic, simple layer architecture.

Now, I need an audit trail of everything that happens in the system, and for that I've created an EventService with methods storeStartEvent(EntityForm form), storeFailEvent(EntityForm form, List errors) and storeSavedEvent(Entity entity) . So three events: "start creating an Entity", "Entity created successfully", and "error creating an Entity". The EntityForm is just a data transfer object which is used by the Service to create an actual Entity.

My Controller takes care of storing the "started" event and the "failure" event, as the Service is not needed in the first case, and in the latter case the Service throws an exception that the Controller catches. The Service still saves the "success" event by itself if it manages to save the Entity successfully.

My audit trail logs the entities, parameters and timestamps alright, but I'd still like to make it log the IP address of the client. That's not an issue for the "started" and "failed" events, as the Controller knows the client's IP address. The issue is the "saved" event. I've got a very clean service.save(EntityForm form) method which I'm using, so I'm wondering about the best way to proceed.

Options I have considered:

  • service.save(EntityForm form, InetAddress ipAddress) seems just wrong, as the IP address would only be used for logging purposes.

  • service.save(EntityFormWithMetadata form) with a new container containing the IP address would be slightly better, but for now that's the only metadata, so I'm not too happy about this one either.

  • Using a thread local variable. I could store the IP address in the Controller into a thread local variable and access it in the EventService. I'm leaning towards this one, but I don't know.

  • Moving the "saved" event storage into the Controller. I could do this, but I'd like to make sure that if the system gets another interface for creating Entities, the "saved" event is still logged. By integrating that event logging into the save method of the service, it's impossible to forget about creating that event.

  • Refactoring the event creation somehow and using somehow using Java @Annotations for methods that need this kind of logging. This would be the coolest solution, but I don't know how to proceed, as all the events have different signatures: Entity, EntityForm, and EntityForm with List.

Can anyone provide any insight?

Best Answer

Using a thread local variable. I could store the IP address in the Controller into a thread local variable and access it in the EventService. I'm leaning towards this one, but I don't know.

I've used this approach before, for storing contextual information about requests. I would perhaps expand from the above to store a thread-local request context object (one attribute of which would be the IP address, you could also store the requesting user id etc. etc.)

It's a clean and simple means to achieving a solution which otherwise will impact all your code paths. I think it's clean because the contextual information is unrelated to the individual method calls in your program logic. It does rely on the server giving each request it's own thread, but so long as you're aware of that then I think it's a useful approach.

I've seen this done to an elaborate degree where the contextual information is built up as the request touches different entities - the entity ids are recorded along with significant events - and the resultant contextual object is available for future diagnosis (by being persisted separately). It provides a powerful alternative to searching through log files of multi-threaded servers.

Related Topic