Business Trace Logging Design Pattern: From Theory to Practice

The article explains a structured business‑trace logging pattern that records each critical step of a workflow as a node, stores logs in a dedicated MySQL database, aggregates them by a business number, and presents them in a sortable, color‑coded front‑end for both developers and business users.

The Dominant Programmer
The Dominant Programmer
The Dominant Programmer
Business Trace Logging Design Pattern: From Theory to Practice

Business trace logging records each critical step of a complex business flow as a structured "node" and persists the data in an independent MySQL database. Nodes share a unique business number (sourcingBizNo) that enables aggregation and query by business number, and the front‑end displays node information as text, tables, or JSON.

Overall Architecture

┌─────────────────────────────────────────────────────────┐
│               Business Service (Producer)               │
│  businessMethod() {                                    │
│    step1 → send node1 log                               │
│    step2 → send node2 log                               │
│    ...                                                 │
│    return → send final result node                     │
│  }                                                     │
└────────────────────┬────────────────────────────────────┘
                     │ sendAsync (asynchronous, non‑blocking)
                     ▼
               ┌─────────────────────┐
               │      RocketMQ       │
               └─────────────────────┘
                     │ consume
                     ▼
               ┌─────────────────────┐
               │ Trace Service (Consumer) │
               │ 1. deserialize message   │
               │ 2. extract query fields│
               │    from "source" node │
               │    → main table        │
               │ 3. write node content  │
               │    → detail table      │
               │ 4. update main table   │
               │    with duration from  │
               │    final node          │
               └───────────────────────┬───────────────────────┘
                                    │
                                    ▼
               ┌─────────────────────┐
               │        MySQL        │
               │ main table: sourcing_trace (bizNo, status, duration…)│
               │ detail table: sourcing_trace_node (nodeName, order, content…)│
               └───────────────────────┬───────────────────────┘
                                    │
                                    ▼
               ┌─────────────────────┐
               │   Front‑end UI      │
               │ input bizNo → query main & detail tables → sort by nodeOrder │
               │ display each node: name, tag, content (text/table/JSON)   │
               └───────────────────────┘

Core Concepts

Business Number (sourcingBizNo) : unique identifier shared by all nodes of the same request, analogous to a traceId.

Node : a step with fields nodeName: name of the step (e.g., "参数校验"). nodeOrder: integer used by the front‑end for ordering. status: SUCCESS or FAIL. content: structured data payload. nodeTag: short result summary (2‑4 characters). nodeTagColor: background color for the tag (green for success, red for failure).

ContentItem Types (mixed within a node):

TEXT : key‑value pair displayed as label: value, optionally colored.

TABLE : tabular data with headers.

JSON : raw JSON shown in a code block with a copy button.

nodeTag : provides an at‑a‑glance result; green background #E6FFEC for success, red background #FFECE8 for failure.

Simple Example: Order Placement Flow

The example assumes an order API with the steps: validate parameters → check stock → deduct stock → create order.

Node Definition

1 – 下单入参 : record raw request.

2 – 参数校验 : validate product, quantity, address.

3 – 库存检查 : verify stock sufficiency.

4 – 创建订单 : generate order number.

99 – 最终结果 : total duration and success/failure flag.

Implementation Code

@Service
public class OrderService {
    @Resource
    private SourcingTraceProducer traceProducer;

    public OrderResult createOrder(OrderRequest request) {
        long startTime = System.currentTimeMillis();
        String bizNo = generateBizNo(); // generate business number
        try {
            // ===== Node 1: Order Request =====
            traceProducer.sendAsync(SourcingTraceMessage.builder()
                .sourcingBizNo(bizNo)
                .sourcingType("ORDER")
                .systemSource("S001")
                .scenarioName("普通下单")
                .nodeName("下单入参")
                .nodeOrder(1)
                .nodeTime("2026-05-21 10:00:01")
                .status("SUCCESS")
                .nodeTag("通过")
                .nodeTagColor("#E6FFEC")
                .content(Arrays.asList(
                    ContentItem.text("商品编码", request.getProductCode()),
                    ContentItem.text("下单数量", String.valueOf(request.getQty())),
                    ContentItem.jsonObj("完整入参", request))
                .build());

            // ===== Node 2: Parameter Validation =====
            validateParams(request); // may throw
            traceProducer.sendAsync(SourcingTraceMessage.builder()
                .sourcingBizNo(bizNo)
                .sourcingType("ORDER")
                .systemSource("S001")
                .scenarioName("普通下单")
                .nodeName("参数校验")
                .nodeOrder(2)
                .nodeTime("2026-05-21 10:00:01")
                .status("SUCCESS")
                .nodeTag("校验通过")
                .nodeTagColor("#E6FFEC")
                .content(Arrays.asList())
                .build());

            // ===== Node 3: Stock Check =====
            int stockQty = checkStock(request.getProductCode());
            boolean enough = stockQty >= request.getQty();
            traceProducer.sendAsync(SourcingTraceMessage.builder()
                .sourcingBizNo(bizNo)
                .sourcingType("ORDER")
                .systemSource("S001")
                .scenarioName("普通下单")
                .nodeName("库存检查")
                .nodeOrder(3)
                .nodeTime("2026-05-21 10:00:02")
                .status(enough ? "SUCCESS" : "FAIL")
                .nodeTag(enough ? "库存充足" : "库存不足")
                .nodeTagColor(enough ? "#E6FFEC" : "#FFECE8")
                .content(Arrays.asList(
                    ContentItem.text("当前库存", String.valueOf(stockQty)),
                    ContentItem.text("需求数量", String.valueOf(request.getQty()))))
                .build());
            if (!enough) {
                throw new RuntimeException("库存不足");
            }

            // ===== Node 4: Create Order =====
            String orderNo = doCreateOrder(request);
            traceProducer.sendAsync(SourcingTraceMessage.builder()
                .sourcingBizNo(bizNo)
                .sourcingType("ORDER")
                .systemSource("S001")
                .scenarioName("普通下单")
                .nodeName("创建订单")
                .nodeOrder(4)
                .nodeTime("2026-05-21 10:00:03")
                .status("SUCCESS")
                .nodeTag("创建成功")
                .nodeTagColor("#E6FFEC")
                .content(Arrays.asList(ContentItem.text("订单号", orderNo)))
                .build());

            // ===== Node 99: Final Result (Success) =====
            long costMs = System.currentTimeMillis() - startTime;
            traceProducer.sendAsync(SourcingTraceMessage.builder()
                .sourcingBizNo(bizNo)
                .sourcingType("ORDER")
                .systemSource("S001")
                .scenarioName("普通下单")
                .nodeName("最终结果")
                .nodeOrder(99)
                .nodeTime("2026-05-21 10:00:03")
                .status("SUCCESS")
                .nodeTag("下单成功")
                .nodeTagColor("#E6FFEC")
                .content(Arrays.asList(
                    ContentItem.text("总耗时", costMs + "ms"),
                    ContentItem.jsonObj("返回结果", result)))
                .build());
            return result;
        } catch (Exception e) {
            // ===== Node 99: Final Result (Failure) =====
            long costMs = System.currentTimeMillis() - startTime;
            traceProducer.sendAsync(SourcingTraceMessage.builder()
                .sourcingBizNo(bizNo)
                .sourcingType("ORDER")
                .systemSource("S001")
                .scenarioName("普通下单")
                .nodeName("最终结果")
                .nodeOrder(99)
                .nodeTime("2026-05-21 10:00:03")
                .status("FAIL")
                .nodeTag("下单失败")
                .nodeTagColor("#FFECE8")
                .content(Arrays.asList(
                    ContentItem.text("总耗时", costMs + "ms"),
                    ContentItem.text("失败原因", e.getMessage())))
                .build());
            throw e;
        }
    }
}

Front‑end Display Example

业务号:ORD260521100001
场景:普通下单
状态:成功
耗时:120ms
节点列表:
┌────┬──────────┬──────────┬─────────────────────────────┐
│ 序号 │ 节点名称   │ 标签      │ 内容摘要                     │
├────┼──────────┼──────────┼─────────────────────────────┤
│ 1   │ 下单入参   │ [通过]    │ 商品:ABC001, 数量:10          │
│ 2   │ 参数校验   │ [校验通过]│                               │
│ 3   │ 库存检查   │ [库存充足]│ 当前库存:50, 需求:10          │
│ 4   │ 创建订单   │ [创建成功]│ 订单号:SO260521001           │
│ 99  │ 最终结果   │ [下单成功]│ 总耗时:120ms                 │
└────┴──────────┴──────────┴─────────────────────────────┘

Design Highlights

Asynchronous sending : sendAsync pushes logs to MQ; failures are logged only and do not block the main business flow.

Business number throughout : all nodes of the same request share the same bizNo, which is the sole key for aggregation.

Unordered arrival : MQ does not guarantee order; the front‑end sorts by nodeOrder.

nodeTag as a quick conclusion : short text with explicit success/failure semantics and a background color.

Special nodes : the first node extracts query fields for the main table; the final node records total duration.

Double try‑catch pattern : each step logs success or failure; an outer catch guarantees the final node records an overall FAIL.

Utility method extraction : repeated message‑building logic can be moved to a helper method sendTraceLog(...).

Constant management : status strings, colors, and tag colors are defined in a dedicated TraceConstants class to avoid hard‑coding.

Utility Method Example

private void sendTraceLog(String bizNo, String nodeName, int nodeOrder,
                           String status, List<ContentItem> content,
                           String nodeTag, String nodeTagColor) {
    SourcingTraceMessage message = SourcingTraceMessage.builder()
        .sourcingBizNo(bizNo)
        .sourcingType("ORDER")
        .systemSource("S001")
        .scenarioName("普通下单")
        .nodeName(nodeName)
        .nodeOrder(nodeOrder)
        .nodeTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
        .status(status)
        .nodeTag(nodeTag)
        .nodeTagColor(nodeTagColor)
        .content(content)
        .build();
    traceProducer.sendAsync(message);
}

Constant Definition

public final class TraceConstants {
    // Status
    public static final String STATUS_SUCCESS = "SUCCESS";
    public static final String STATUS_FAIL = "FAIL";
    // Content text colors
    public static final String COLOR_SUCCESS = "#52c41a"; // green text
    public static final String COLOR_FAIL = "#ff4d4f";    // red text
    // Tag background colors
    public static final String TAG_COLOR_SUCCESS = "#E6FFEC"; // light green
    public static final String TAG_COLOR_FAIL = "#FFECE8";    // light red
}

Applicable Scenarios

Processes with many steps where pinpointing the problematic step is essential.

When business users (non‑developers) need to understand execution status.

Fast location of issues by business number.

Workflows that involve multiple external system calls and require logging of each call's input and output.

Unsuitable Scenarios

Simple CRUD operations.

Ultra‑low‑latency, high‑frequency interfaces where even minimal async overhead is unacceptable.

Purely technical problems that do not involve business‑user investigation.

Design Details

Async send : traceProducer.sendAsync(message) logs failures only, keeping the main flow non‑blocking.

Business number continuity : the same bizNo is used for every node, enabling a single‑key aggregation query.

Unordered MQ delivery : messages may arrive out of order; front‑end ordering relies on nodeOrder.

nodeTag design : short (2‑4 characters), clear success/failure meaning, colored background for instant visual cue.

Special node conventions :

First node (input parameters) extracts fields such as customer code or product code for the main table, supporting list‑page search.

Final node extracts total duration and writes it to the main table.

Double try‑catch pattern ensures:

Each step records FAIL when an exception occurs.

The final node always records the overall outcome (SUCCESS or FAIL).

Subsequent steps are not logged after a failure.

Utility method extraction reduces repetitive builder code when many nodes are logged.

The core idea is to record every critical step of a business process in a structured way, allowing anyone to quickly see what happened, at which step, and why it failed, by querying a single business number.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javabackend designMySQLrocketmqasynchronous messagingbusiness trace logging
The Dominant Programmer
Written by

The Dominant Programmer

Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi

0 followers
Reader feedback

How this landed with the community

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.