Backend Development 11 min read

Design and Implementation of a Unified Log Recording Framework for Java Backend Systems

This article introduces a unified log recording solution for Java backend services, covering the background need, expected UI effects, database schema, core principles, detailed annotation and service implementations, and multiple usage scenarios including simple log objects, inheritance, and nested objects.

Architect's Guide
Architect's Guide
Architect's Guide
Design and Implementation of a Unified Log Recording Framework for Java Backend Systems

1. Background

Backend systems increasingly require log recording functionality, and existing log implementations are not reusable; a unified log format is needed to improve development efficiency.

2. Expected Effect Display

New action:

Modify action:

Delete action:

3. Data Storage

Note: other storage methods can be used; the following is a simple example.

`biz_id` bigint(20) NOT NULL DEFAULT 0 COMMENT 'business id',
`biz_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT 'business type',
`operator_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'operator',
`operate_content` text COMMENT 'operation content',
`change_before` text COMMENT 'before change',
`change_after` text COMMENT 'after change',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'creation time'

4. Principle Brief

The log construction focuses on two objects: the state before modification and the state after modification.

Before: null → After: X = create

Before: Y → After: X = update

Before: Y → After: null = delete

The system compares each property of the two objects; if a property changes, it is recorded in the log. Fields to be logged are marked with a custom annotation.

5. Specific Implementation

Annotation definition:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LogField {
    String name() default "";
    String valueFun() default "";
    boolean spliceValue() default true;
}

Service class (simplified):

@Service
@Slf4j
public class OperateLogService {
    @Resource
    private CommonOperateLogService commonOperateLogService;

    enum ActionEnum {
        ADD("新建"),
        UPDATE("修改"),
        DELETE("删除");
        public String desc;
        ActionEnum(String desc) { this.desc = desc; }
    }

    private int insertLog(CommonOperatorLog log) {
        String result = commonOperateLogService.insertLog(JSON.toJSONString(log));
        Response response = JSON.parseObject(result, Response.class);
        return (response == null || ApiResponse.Status.fail.equals(response.getStatus())) ? 0 : (int) response.getContent();
    }

    public
void saveLog(String operatorId, Long bizId, Integer bizType, T target, T original) {
        if (StringUtils.isBlank(operatorId) || (target == null && original == null)) {
            throw new IllegalArgumentException();
        }
        if (target != null && original != null && !target.getClass().isAssignableFrom(original.getClass())) {
            throw new IllegalArgumentException();
        }
        ActionEnum action = getAction(target, original);
        List
> changeInfos = getChangeInfoList(target, original);
        List
changeInfoList = new ArrayList<>();
        if (CollectionUtils.isEmpty(changeInfos) && action != ActionEnum.UPDATE) {
            changeInfoList.add(0, action.desc);
        } else if (!CollectionUtils.isEmpty(changeInfos)) {
            for (Triple
i : changeInfos) {
                String part = action.desc + (i.getRight().spliceValue() ? "为:" + i.getLeft() + i.getMiddle() : "了" + i.getLeft());
                changeInfoList.add(part);
            }
        } else {
            return;
        }
        String operateContext = StringUtils.join(changeInfoList, "\n").replaceAll("\"", "").replaceAll("\\[", "").replaceAll("\\]", "");
        CommonOperatorLog operatorLog = new CommonOperatorLog();
        operatorLog.setBizId(bizId);
        operatorLog.setBizType(bizType);
        operatorLog.setOperateContent(operateContext);
        operatorLog.setOperatorId(operatorId);
        operatorLog.setChangeBefore(JSON.toJSONString(original));
        operatorLog.setChangeAfter(JSON.toJSONString(target));
        this.insertLog(operatorLog);
    }

    private ActionEnum getAction(Object target, Object original) {
        if (target != null && original != null) return ActionEnum.UPDATE;
        if (target != null) return ActionEnum.ADD;
        if (original != null) return ActionEnum.DELETE;
        return ActionEnum.ADD;
    }

    // Methods getChangeInfoList, compareTo, allFields, findField, getFieldName, getFieldValue, getStrValue omitted for brevity – they implement reflection‑based field comparison using the @LogField annotation.
}

6. Usage Examples

1) Log object dedicated to logging:

public class SubsidyRateLog {
    @LogField(name = "补贴率名称")
    private String name;

    @LogField(name = "适用城市", valueFun = "getCityNames")
    private List
cityIds;

    private List
cityNames;
}

Here name is logged directly, while cityIds uses getCityNames to obtain a displayable value.

2) No dedicated log object – inherit from existing entity:

public class SubsidyRate {
    @LogField(name = "补贴率名称")
    private String name;

    @LogField(name = "适用城市", valueFun = "getCityNames")
    private List
cityIds;
}

@Data
public class SubsidyRateLog extends SubsidyRate {
    private List
cityNames;
}

This approach preserves the original entity while adding logging fields.

3) Objects containing nested objects:

public class SubsidyRateLog {
    @LogField(name = "补贴率名称")
    private String name;

    @LogField
    private Address address;
}

public class Address {
    @LogField(name = "省份")
    private String province;

    @LogField(name = "城市")
    private String city;
}

If the nested object has no @LogField annotations, its whole value is logged; otherwise only annotated fields are considered.

7. End Notes

The article concludes with a call to follow the public account for more architecture guidance and provides several promotional links to external resources.

backendJavaDatabaseSpringloggingAnnotationsCode Example
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.