Search code examples
javaloggingaspectjaudit-logging

Does exist some better manner to logger update history?


In our back-end systems, there are many situations to update object, e.g. update goods, update goods prices, update user, update status, update order(cancel, update price) and so on. Now some object we have a specially log table, e.g. order_update_log , to logger update operations. Some only logger to file, e.g.

logger.info("goods update: before: {}, after: {}", oldGoods, newGoods)

Feel process these trivial things very annoying, does exist some better manner/tools to process logger like this?


Solution

  • There is very nice thing called Aspects. You can write aspect for you project that is only aware of tracking changes of goods objects. Supposing you have Goods class:

    public class Goods {
        private Integer id;
        private String name;
        private BigDecimal cost;
    
        // Getters, Setters
        ...
    }
    

    which mapped to database table goods with corresponding fields:

    CREATE TABLE `goods` (
        `id` INT NOT NULL AUTO_INCREMENT,
        `name` varchar NOT NULL,
        `cost` DECIMAL NOT NULL,
        PRIMARY KEY (`id`)
    );
    

    Supposing you have GoodsService:

    public class GoodsService {
    
        public Goods read(Integer id) {
            Goods goods = /* Read goods from database */
            return goods;
        }
    
        public void write(Goods goods) {
            /* Write goods to database */
        }
    }
    

    and GoodsController that using GoodsService:

    public class GoodsController {
    
        private GoodsService goodsService;
    
        public void updateGoods(Integer id, String name, BigDecimal cost) {
            Goods goods = goodsService.read(id);
            goods.setName(name);
            goods.setCost(cost);
            goodsService.write(goods);
        }
    
    }
    

    So lets add some aspects magic to your project for tracking changes of goods. Now create another table with additional fields for storing user and datetime of updating goods object:

    CREATE TABLE `goods_log` (
        `revision` INT NOT NULL AUTO_INCREMENT,
        `id` INT NOT NULL,
        `name` varchar NOT NULL,
        `cost` DECIMAL NOT NULL,
        `user` varchar NOT NULL,
        `timestamp` DATETIME NOT NULL,
        PRIMARY KEY (`revision`,`id`)
    );
    

    Write aspect class:

    @Aspect
    public class GoodsLogger {
        @Before(value = "execution(* org.antonu.service.GoodsService.write(..)) && args(goods)")
        public void logWrite(Goods goods) {
            // Get current user and timestamp,
            // Write it to goods_log table, as well as goods data
        }
    }
    

    That's it! Your business logic code didn't change, it's clear to read. But every time GoodsService's write method invoked you will get record in goods_log with new state of goods.

    To make this code working it should be compiled with AJC compiler, also aspectjrt library should be included. It can be easily done via maven:

    <properties>
        <aspectj.version>1.8.7</aspectj.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>1.4</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjrt</artifactId>
                        <version>${aspectj.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjtools</artifactId>
                        <version>${aspectj.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
    

    Modern IDE Intellij Idea should automatically use AJC compiler with maven config above. If not, you must configure compiler via File - Settings - 'Build, Execution, Deployment' - Compiler - Java Compiler - Use compiler: Ajc. Path to AJC compiler: <path_to>/aspectjtools-1.8.7.jar