# log-record: Annotation Driven Operation Logs for Spring Applications

> log-record is a Java library that records operation logs through an annotation on the method, resolving the values with Spring Expression Language expressions, with support for a manually populated context, custom functions in the expression parser, entity diffs, conditional recording, retries with a fallback and a pluggable output pipeline. It ships as two Spring Boot starters.

**qqxx6661/log-record** — 使用注解优雅记录系统日志，操作日志，后端埋点等，支持SpEL表达式，自定义上下文，自定义函数，实体类DIFF等其他高阶处理。Elegantly record system logs, operation logs, backend tracking events, and more using annotations, with support for SpEL expressions, custom contexts, custom functions, entity diffs, and other advanced features.

- Repository: https://github.com/qqxx6661/log-record
- Stars: 1,065 · Forks: 193
- Language: Java
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/qqxx6661-log-record

## Operation Logs Without Log Calls in the Business Method

log-record exists to solve a specific problem with operation logs. The README starts from the pattern every Java team has seen: a list of operation log entries with a business identifier, a message and an operator, and asks how to record that elegantly in code.

The crude version is a helper class and a String.format, and the README is blunt about the result. That approach causes the business code to be invaded by the logging code, and for code readability and maintainability that is a disaster.

The annotation version puts the log declaration where the method is declared. The shape of it is a single annotation on the method:

```java
@OperationLog(bizType = "'followerChange'", bizId = "#request.orderId", msg = "...")
public Response<T> function(Request request) {
  // 业务执行逻辑
}
```

The three attributes are the business type, the business identifier and the message, and the message and the identifier are expressions rather than literals. The claim in the README is that the logging is not invasive, and the feature list puts it as a headline: the log aspect throwing an exception never affects the execution of the original method.

That last point is the design decision that makes the approach acceptable. A logging library that can break the operation it is observing is worse than no library, so the aspect is written so that its own failures are swallowed.

## SpEL Is What Makes the Annotation Expressive

The interesting part of the design is where the values in the annotation come from, and the answer is Spring Expression Language.

The README walks through the progression. With a literal annotation you can record that a follower changed, but not the order identifier or the old and new values, because those are not known at compile time. SpEL solves the first part, because it can reach the method arguments, so the order identifier becomes #request.orderId and the new address becomes #request.newFollower.

That is the standard use of SpEL, and it is enough for a large class of cases. The message then reads as a sentence with interpolated values, for example a string literal for the fixed words and an expression for each value that changes.

The point about the design is that the expression is evaluated when the method is called, not when the annotation is declared, so the same annotation can be correct for a thousand different arguments.

The README also links the SpEL documentation, which is worth reading if you plan to write more than simple property references, since SpEL is a full expression language with its own rules, and a mistake in an expression is a runtime failure rather than a compile time one.

## Custom Context and Custom Functions, and the Honest Trade-off

The problem SpEL does not solve on its own is values that are not arguments. A user's name, or the previous value of a field, usually has to be looked up in the database, and the arguments of the method generally do not contain it.

The first solution in the README is a context variable, LogRecordContext, where the code manually passes in the values it has computed so that SpEL can then resolve them. The pattern is that a value is put into the context and the expression reads it by name.

The README is refreshingly honest about that step. It asks whether this is not invasive of the business logic again, and answers that it is, but that the method is convenient and easy to understand, with no difficulty in comprehension.

The better answer, which the README then gives, is custom functions. SpEL supports user-defined functions inside expressions, so the lookup methods can be registered in the SpEL parser in advance, and SpEL will call the corresponding function while evaluating the expression. The annotation then reads as a call to those functions directly, with the argument as the parameter, and the business method contains no logging code at all.

So the library offers two levels: a context you populate yourself, which is simple and slightly invasive, and registered functions, which are clean and require a small amount of setup. The feature list names both, along with support for a global operator identifier and for specifying the log data pipeline, so you can write to a database or to a system such as TLog, or to a message queue.

## The LogDTO and What Actually Gets Recorded

The log entity, LogDTO, is what the pipeline receives, and the field list in the README is effectively the data contract of the library.

It contains a logId, which is a generated UUID, and the bizId and bizType, which are the business identifier and type that came from the annotation. It records the exception, which is written when the function fails, and the operateDate, the time the operation ran. There is a success flag for whether the function completed, the msg content, and a tag for custom labels.

There are three fields that are more than bookkeeping. The returnStr holds the return value of the method after a successful call, as a string or as a JSON-serialised entity. The executionTime is the duration of the method in milliseconds. And the operatorId identifies who performed the action, with a global operator identifier mechanism in the feature list so you can decide how that is resolved, whether from a thread local, a security context or something else.

The last field is the one that carries the diff. A list of diffDTO holds the entity diff data, including the names of the changed fields, the field values and the class name, and the README gives a worked JSON example of a complex record where the extra field and the msg both spell out that a user number changed from one value to another and a name changed from one value to another.

## Entity Diff Between Objects That Are Not the Same Type

The diff support is listed as a headline feature, and the wording is specific: it supports diffing objects of the same class and even of different classes.

That distinction matters more than it looks. A diff between two objects of the same class is field by field comparison, and the answer is a list of fields whose values differ. A diff between objects of different classes needs a rule about which fields correspond, and the library handles that, which is the case that comes up when a request object is compared with a database entity or with a view model.

The value of the feature is in the output format rather than the mechanics. The diff is recorded as structured data, with the changed field names, the field values and the class names, so a downstream reader can render a change list without parsing a sentence. That is a different artifact from a log message, and the combination is what the README's example shows: the human readable msg and the machine readable diff in the same record.

It is worth being clear about what the diff is and is not. It reports which fields changed between two values at the moment of the call. It is not a change history and it does not know whether the change was sensible.

## Conditions, Retries, Timing and the Output Pipeline

The feature list is long and a few entries are worth pulling out, because they are the ones that decide whether the library fits a project with strict requirements.

Conditional annotation means the log is only written when a Condition is satisfied, and the condition is itself parsed with SpEL. That is the mechanism for not logging read operations, or for logging only failures, without scattering if statements through the business method.

Retry and fallback is configured separately. You can set a number of retries and a fallback handler for when processing fails, with the fallback implemented through an SPI, so a failure to write the log does not lose the record silently and does not take down the request.

The aspect timing can be controlled so the log is written before or after the method executes, and there is support for a custom success judgement, so the library's idea of whether the operation succeeded can differ from whether the method returned normally.

Two more entries matter for production. The log data pipeline is customisable, which is how the record reaches a database, a system such as TLog, or a message queue. And the message thread pool is customisable, so the log writing does not run on the caller's thread. There is also support for repeated annotations on the same method and for recording logs manually without the annotation at all.

## Two Starters for Two Generations of Spring Boot

The distribution is split by Spring Boot generation, and the split is not cosmetic.

For Spring Boot 1 and Spring Boot 2 on JDK 8 and later, the dependency is the log-record-starter artifact in the cn.monitor4all group, with the version to be filled in from the latest release. For Spring Boot 3 on JDK 17 and later, it is the log-record-springboot3-starter artifact, again in the cn.monitor4all group, published to Maven Central.

The overall support claim is that the library works with Spring Boot 1, 2 and 3 across JDK 8 to JDK 21, which is a wide range for a library that has to hook into the framework's aspect machinery.

The repository layout matches that split, with a log-record-core directory for the shared implementation, a log-record-starter directory for the Spring Boot 1 and 2 starter, and a log-record-springboot3-starter directory for the Spring Boot 3 one, alongside the documentation images and four README translations in Simplified Chinese, English, Japanese and Brazilian Portuguese.

One piece of history is worth knowing, and the README records it plainly. The project was originally inspired by a Meituan technical blog article on operational logs, and the README points to the original author's repository for anyone who is looking for that code. This repository implemented most of the features described in the article from scratch, taking on production experience and user feedback along the way, and that is the honest framing of what it is: a from-scratch implementation of a published design.

The releases are v1.7.1 from 2025-07-26, v1.7.0 from 2024-06-01 and v1.6.3 from 2024-05-26, the licence is Apache-2.0, and the last push was on 2026-09-02.

## Conclusion

log-record fits a Spring team that has to show a readable history of who changed what, and that would rather express the change in the method signature than assemble a log call inside the business code. It is a poor fit for a project that needs the recorded values to be correct in every case, since the resolution depends on SpEL expressions and on the methods those expressions call, and it is the wrong choice if you only need application logs rather than an operation audit trail. Before adopting it, check the Spring Boot generation you are on, since the starters are split between Spring Boot 1 and 2 and Spring Boot 3, and note that the recorded log can be pushed to your own pipeline or a message queue rather than written by the library, so you decide where the data lands. The project is Apache-2.0 licensed, the newest release is v1.7.1 from 2025-07-26, and the last push was on 2026-09-02.

## FAQ

### What does log-record do?

It records operation logs through a Java annotation on the method, with the business type, the business identifier and the message given as SpEL expressions so the values are resolved at call time. The library also supports a manually populated context, custom functions registered in the SpEL parser, entity diffs, conditional recording, retries with an SPI fallback and a customisable output pipeline.

### Which Spring Boot versions does log-record support?

Spring Boot 1, 2 and 3 across JDK 8 to JDK 21, with separate starters. The log-record-starter artifact covers Spring Boot 1 and 2 on JDK 8 and later, and the log-record-springboot3-starter artifact covers Spring Boot 3 on JDK 17 and later, both in the cn.monitor4all group on Maven Central.

### What information does a log record contain?

The LogDTO holds a generated UUID logId, the bizId and bizType from the annotation, the exception when the function fails, the operateDate, a success flag, the message, a custom tag, the method return value as a string or JSON, the execution time in milliseconds, extra information, the operator identifier, and a list of diff objects describing changed field names, values and class names.

### How do I log a value that is not a method argument?

Two ways. You can put the computed value into the LogRecordContext and read it in the expression, which the README notes does put a little logging code back into the business method. Or you can register the lookup as a custom function in the SpEL parser, so the expression calls it directly and the method stays clean.

### Can log-record fail without breaking the business method?

The feature list states that the log aspect throwing any exception will not affect the execution of the original method, and there is separate support for configuring a retry count and a fallback handler through an SPI, plus a custom message thread pool so the log writing does not run on the caller's thread.

## Sources

- [Issues](https://github.com/qqxx6661/log-record/issues)
- [License: Apache-2.0](https://github.com/qqxx6661/log-record/blob/master/LICENSE)
- [qqxx6661/log-record on GitHub](https://github.com/qqxx6661/log-record)
- [README](https://github.com/qqxx6661/log-record/blob/master/README.md)
- [Releases](https://github.com/qqxx6661/log-record/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/qqxx6661-log-record
