jdbc-observations / datasource-micrometer

Micrometer Observation API instrumentation and Spring Boot 3 Auto-Configuration for JDBC APIs
Apache License 2.0
50 stars 8 forks source link

When data source creation is before the creation of DataSourceObservationBeanPostProcessor, the DB traces are not present in the output #14

Closed fatihdogmus closed 1 year ago

fatihdogmus commented 1 year ago

Hello,

We recently upgraded to spring boot 3 and since we didn't have any tracing solution before, we decided to adopt to latest and greatest and use micrometer for both application metrics and tracing. We settled on zipkin reporter with otel bridge. Then, for JDBC reporting, we saw that we need this library. So I added the necessary dependencies.

But, we create the datasource manually and this creation happens before the DataSourceObservationAutoConfiguration is triggered, so the DataSourceObservationBeanPostProcessor is not registered yet when the DataSource is created so the DataSource is not decorated with the ProxyDataSource. This results in DB traces not shown in the request trace.

We found a janky workaround like this: We create the DataSource already wrapped in the ProxyDataSource, but without any configurations:

    @Bean
    public DataSource dataSource(DataSourceProperties dataSourceProperties) {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(dataSourceProperties.getUrl());
        hikariConfig.setUsername(dataSourceProperties.getUsername());
        hikariConfig.setPassword(dataSourceProperties.getPassword());
        hikariConfig.setDriverClassName(dataSourceProperties.getDriverClassName());

        HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
        return ProxyDataSourceBuilder.create(hikariDataSource).build();
    }

Then, on another configuration class, we get the DataSourceObservationBeanPostProcessor and the DataSource, and set the necessary proxy config on the ProxyDataSource:

@Configuration
@RequiredArgsConstructor
public class DatasourceBeanPostProcessorConfig {

    private final ObjectProvider<DataSourceObservationBeanPostProcessor> postProcessors;
    private final ObjectProvider<DataSource> dataSource;

    @PostConstruct
    public void init() {
        postProcessors.ifAvailable(processor -> {
            ProxyDataSource proxyDataSource = (ProxyDataSource) processor.postProcessAfterInitialization(dataSource.getIfAvailable(), "dataSource");
            ProxyDataSource originalSource = (ProxyDataSource) dataSource.getIfAvailable();

            originalSource.setProxyConfig(proxyDataSource.getProxyConfig());
        });
    }
}

But this is really not ideal. Are we doing something wrong or is there a bug in ordering on the configuring beans?

Thank you.

ttddyy commented 1 year ago

we create the datasource manually

Can you clarify what this means? Are you defining a DataSource bean in one of your application's config classes? It is not a problem defining its own DataSource bean.

Spring first parses all the bean definition classes(@Configuration). Once config parsing is done, then create beans. Spring creates BeanPostProcessor beans prior to regular beans. When your DataSource bean is created, the DataSourceObservationBeanPostProcessor bean must be already available and handles proxy creation. So, you shouldn't need any code/bean definition to do with proxy creation for your DataSource bean.

Likely not, but if you are creating DataSource outside of spring, and registering it as a singleton bean to the application context, then the BeanPostProcessor doesn't work because it is managed by the outside of spring bean lifecycle.

fatihdogmus commented 1 year ago

Are you defining a DataSource bean in one of your application's config classes?

Yes we create it in a configuration class, similar to what I posted but without the ProxyDataSource stuff.

Spring first parses all the bean definition classes(@Configuration). Once config parsing is done, then create beans.

This is true, but somehow this is not the case for our application. When I put debug points to our DataSource creating factory function, DataSourceObservationAutoConfiguration's bean creating factory method and DataSourceObservationBeanPostProcessor's postProcessAfterInitialization method, first our DataSource bean is created, then the factory method is called, then the postProcessAfterInitialization starts being called, and since DataSource bean is already created, it never goes into the if that decorates the bean.

I don't know why this is the case. We have a multi module project, but that shouldn't affect it right?

I will try to create a minimum setup, but it can be challenging as our application is complex and it might be hard to replicate that in a new demo codebase.

ttddyy commented 1 year ago

I have added a test that uses a DataSource bean, and it is working as expected. For your case, since you see your DataSource bean is created before BeanPostProcessor, I suspect there probably is a BeanPostProcessor that references DataSource beans directly or indirectly, which is causing the early bean initialization behavior.

aliariff commented 9 months ago

The issue remains unresolved and occurs when utilizing Flyway. Flyway's configuration initiates quite early and necessitates a datasource. I can confirm that I've encountered the same problem as described by @fatihdogmus .

ttddyy commented 9 months ago

@aliariff I have locally tested and also looked at the FlywayAutoConfiguration code, it works as expected. Make sure you don't specify spring.flyway.[url|user], otherwise, it creates a new datasource for flyway. If you debug, you can check whether the flyway.configuration.dataSource object has the intended datasource object.

lemartin commented 2 months ago

I can kind of explain, how the same is happening to us. The DataSource bean is initialised before the DataSourceObservationBeanPostProcessor bean, because the DataSource is pulled in, as a dependency, by another BeanPostProcessor.

The call stack for createBean is something like the following:

ttddyy commented 2 months ago

Hi @lemartin If you could provide a minimum reproducible example project, I'll take a look.