FrankChen021 / bithon

An observability platform mainly for Java
Apache License 2.0
18 stars 5 forks source link

java.lang.NoClassDefFoundError: Could not initialize class org.bithon.agent.instrumentation.aop.advice.AroundAdvice #879

Open kai8406 opened 3 weeks ago

kai8406 commented 3 weeks ago

Lettuce works fine, but when replaced with Jedis, the following error is thrown:

 java.lang.NoClassDefFoundError: Could not initialize class org.bithon.agent.instrumentation.aop.advice.AroundAdvice
    at redis.clients.jedis.BinaryJedis.getDB(BinaryJedis.java:3343) ~[jedis-3.3.0.jar:na]

test code

spring-boot: 2.3.12.RELEASE jedis:3.30

pom.xml

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
      <exclusions>
        <exclusion>
          <groupId>io.lettuce</groupId>
          <artifactId>lettuce-core</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <!-- Jedis -->
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
    </dependency>

Java code:

@RestController
@RequestMapping("/redis")
public class RedisController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @PostMapping("/set")
    public String setValue(@RequestParam String key, @RequestParam String value) {
        stringRedisTemplate.opsForValue().set(key, value);
        return "Value set successfully!";
    }

    @GetMapping("/get")
    public String getValue(@RequestParam String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }
}
curl -X POST "http://localhost:8080/redis/set?key=testKey&value=testValue1"
curl -X GET "http://localhost:8080/redis/get?key=testKey"

The constructor of Jedis3 BinaryJedis doesn't have a getDB() method. After commenting out the related code, the ClassCastException is fixed and the agent works normally.

@Override
    public InterceptionDecision before(AopContext aopContext) throws Exception {
        Jedis jedis = aopContext.getTargetAs();
        String hostAndPort = jedis.getClient().getHost() + ":" + jedis.getClient().getPort();

        // BUG: The constructor of Jedis3 BinaryJedis doesn't have a getDB() method.
        // long db = jedis.getDB();
        long db = 0;

        String command = aopContext.getMethod().toUpperCase(Locale.ENGLISH);

        // Keep the metrics in current thread local so that Input and Output stream can access them
        InterceptorContext.set("redis-command",
                               new JedisContext(metricRegistry.getOrCreateMetrics(hostAndPort, command)));

        if (!ignoreCommands.contains(command)) {
            ITraceSpan span = TraceContextFactory.newSpan("jedis");
            if (span == null) {
                return InterceptionDecision.SKIP_LEAVE;
            }

            // BUG:fix ClassCastException, Client -> Jedis
            // Client redisClient = aopContext.getTargetAs();
            Jedis redisClient = aopContext.getTargetAs();
            aopContext.setSpan(span.method(aopContext.getTargetClass().getName(), command)
                                   .kind(SpanKind.CLIENT)
                                   .tag(Tags.Net.PEER,
                                        redisClient.getClient().getHost() + ":" + redisClient.getClient().getPort())
                                   .tag(Tags.Database.REDIS_DB_INDEX, db)
                                   .tag(Tags.Database.SYSTEM, "redis")
                                   .start());
        }

        return InterceptionDecision.CONTINUE;
    }

However, it includes some unnecessary commands.

image

How can we filter out these unnecessary commands globally?

kai8406 commented 3 weeks ago

Moreover, figuring out how to get the database (db) is also an issue.

FrankChen021 commented 3 weeks ago

did i miss sth? I see the getDB method exists in the 3.3.0 package

image
FrankChen021 commented 3 weeks ago

How can we filter out these unnecessary commands globally?

two options:

  1. we can provide a configuration at the agent level for the redis plugin to filter
  2. at the server side pipeline, we can configure drop rule to drop commands that we don't care about
    1. check src/main/resources/schema/redis-metrics.json
    2. add a 'drop' transformer like:
      {
        "type": "drop",
        "expr": "command == 'QUIT'"
      }
    3. use the /api/datasource/schema/update API to update the schema at runtime

I would like to prefer to the later one

kai8406 commented 3 weeks ago

How can we filter out these unnecessary commands globally?

two options:

  1. we can provide a configuration at the agent level for the redis plugin to filter
  2. at the server side pipeline, we can configure drop rule to drop commands that we don't care about

    1. check src/main/resources/schema/redis-metrics.json
    2. add a 'drop' transformer like:
      {
      "type": "drop",
      "expr": "command == 'QUIT'"
      }
    3. use the /api/datasource/schema/update API to update the schema at runtime

I would like to prefer to the later one

Excellent feature, very useful. In my personal testing, this expression doesn't support 'AND', can multiple drops only be configured through array format?