Search code examples
javatomcatcross-cutting-concerns

Gather data across layers for writing audit logs


Background

The application in question is a Java web-app deployed in Tomcat and uses Apache CXF for exposing REST services. I found Spring configuration, but not sure how well it is wired up to the CXF infrastructure.

Every time something important happens, like an entity is updated or deleted, I'd like to log information including (but not limited to):

  1. What happened
  2. Who made the request
  3. What entity was changed
  4. New state of the entity if applicable
  5. Information about any notification emails sent
  6. etc...

Current Setup & challenge

We have several layers and the calls look like this: Controller > Service > Data Layer > Utils (Email, SMS sending etc...).

The problem is, the data I want to gather and log, begins to be available and is only available in separate layers. Some times the data is passed through arguments to the next layer, but other times they are not passed because the data isn't required in other layers. Here are some examples:

  • Some HTTP request related data is only used for some validation, and is not sent to the service layers.
  • Some times, core entity data is only available in service and data layers and isn't available to the controllers.
  • Various layers of Utils generate some final pieces of data (like links, codes, final email content) that isn't known to any other layer.

But I need to gather such information from across all layers and log them for audit purpose, as a single log line.

Constraints

  • The code is not designed well in terms of respecting layer boundaries. So, sometimes things are passed around where the shouldn't. But I can't continue to do this.
  • I can't return all the required data to the controller and finally log them there, because the intermediate layers' methods are called in a lot of places, and is not practical to refactor them all.
  • I can't log them in different places separately as the requirement doesn't allow that. If there isn't any other way, I can push this strategy and have a common request ID or something and multiple log lines, to correlate them.

My question is, how and where should I gather all this data and write them to the audit log? What is the best practice for doing the above, in a Java web application?


Solution

  • Sounds like your application is a mess.

    However it should still be possible to do this as long as everything is triggered based on a request.

    Here's two approaches:

    1) Use the MDC object from a logging framework like Logback. This object can be filled on a request basis with keys where you can attach arbitrary information that can be later extracted for logging (or it can be automatically attached as additional fields to your log message). Read up on it here: https://logback.qos.ch/manual/mdc.html

    It would be my recommended solution as it closely matches what you are trying to achieve. If that's not possible then:

    2) Create an object with static access to a ThreadLocal that you use for the purpose of keeping track of information accross various layers. Log it in a HTTP Filter when requests finish. And make sure to clear the information again so it is not mixed up with the next request that uses the same thread.

    If you don't want to pollute your code with audit logging, you may consider using Aspects (Aspect Orientated Programming) to add this functionality to your classes.