micrometer-metrics/micrometer

[jOOQ] MetricsDSLContext calls time() twice on fetchValue(SelectField) path, causing tag loss

Open

#6659 aperta il 23 ago 2025

Vedi su GitHub
 (4 commenti) (3 reazioni) (0 assegnatari)Java (935 fork)batch import
bughelp wantedinstrumentationmodule: micrometer-core

Metriche repository

Star
 (4220 star)
Metriche merge PR
 (Merge medio 1g 21h) (100 PR mergiate in 30 g)

Descrizione

Describe the bug

When using MetricsDSLContext with jOOQ 3.20.x, calling DSLContext#fetchValue(SelectField) leads to double instrumentation: time() is invoked twice along the select(...) path. This causes tag state to be reset/overwritten and results in missing or unexpected tags on the recorded jooq.query timer. This occurs even if Micrometer is compiled against jOOQ 3.14.x, because at runtime jOOQ 3.20.x introduces a new overload that changes the call path.

Environment

- Micrometer version: 1.15.3
- jOOQ version: 3.20.6
- Java version: 21

To Reproduce

Minimal JUnit test that demonstrates the issue: How to reproduce the bug:

@Test
void jooqMethodTest() throws SQLException {
    try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:test")) {
        Configuration config = new DefaultConfiguration().set(conn).set(SQLDialect.H2);
        MeterRegistry registry = new SimpleMeterRegistry();

        MetricsDSLContext jooq =
            MetricsDSLContext.withMetrics(DSL.using(config), registry, Tags.empty());

        Integer result = jooq.tag("name", "checkAuthorExists")
                             .fetchValue(DSL.inline(123));

        assertThat(registry.get("jooq.query")
                .tag("name", "checkAuthorExists")
                .tag("type", "read")
                .timer()
                .count()).isEqualTo(1);
    }
}

In practice, the assertion fails because the timer is not recorded with the expected tags (or the meter cannot be found under that tag set).

Expected behavior

A single jooq.query timer is recorded once with the expected tags, e.g. name=checkAuthorExists, type=read.

Additional context

Call path (showing the double time()):

DefaultDSLContext.fetchValue(DSL.inline(123)) -> fetchValue(SelectField<T> field)
  -> DefaultDSLContext.fetchValue(select(field))
      -> MetricsDSLContext.select(SelectField<T1> field1)                 // time() #1
          -> DefaultDSLContext.select(SelectField<T1> field1)
              -> MetricsDSLContext.select(SelectFieldOrAsterisk... fields) // time() 

Root cause (fetchValue() in jOOQ 3.20.x):

@Override
public <T> T fetchValue(SelectField<T> field) {
    return field instanceof TableField
         ? fetchValue((TableField<?, T>) field)
         : field instanceof Table<?>
         ? fetchValue(select(field).from((Table<?>) field))
         : fetchValue(select(field));
}

Because MetricsDSLContext instruments select(...), the new delegation path results in double instrumentation and tag loss.

So, Please add a compatibility note to the official Micrometer documentation for for users running jOOQ newer(i.e., latest versions), informing users that certain newly added overloads (e.g., DefaultDSLContext#fetchValue(SelectField)) explaining that certain newly added overloads (e.g., DefaultDSLContext#fetchValue(SelectField)) may internally delegate to select(...), which can cause double instrumentation and tag loss with MetricsDSLContext unless Micrometer provides corresponding overrides.

Guida contributor