From f366503d58aa7e3395d88f6c70e6ea885fc50d2e Mon Sep 17 00:00:00 2001 From: xzyJavaX <49760141+xzyJavaX@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:46:39 +0800 Subject: [PATCH] Enhance lettuce plugin to adopt uniform tags (#441) --- CHANGES.md | 2 +- .../lettuce/v5/LettucePluginConfig.java | 116 ++++++++++++++++++ .../v5/RedisChannelWriterInterceptor.java | 56 ++++++--- .../v5/RedisChannelWriterInterceptorTest.java | 3 +- apm-sniffer/config/agent.config | 4 + .../java-agent/configurations.md | 2 + .../lettuce-scenario/config/expectedData.yaml | 18 ++- 7 files changed, 172 insertions(+), 29 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1bd7aa0d7..38c8a288a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Release Notes. 8.15.0 ------------------ - +* Enhance lettuce plugin to adopt uniform tags. #### Documentation diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/LettucePluginConfig.java b/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/LettucePluginConfig.java index 57cd760a8..4f6668d32 100644 --- a/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/LettucePluginConfig.java +++ b/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/LettucePluginConfig.java @@ -20,6 +20,10 @@ package org.apache.skywalking.apm.plugin.lettuce.v5; import org.apache.skywalking.apm.agent.core.boot.PluginConfig; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class LettucePluginConfig { public static class Plugin { @PluginConfig(root = LettucePluginConfig.class) @@ -36,6 +40,118 @@ public class LettucePluginConfig { * Set a negative number to save specified length of parameter string to the tag. */ public static int REDIS_PARAMETER_MAX_LENGTH = 128; + + /** + * Operation represent a cache span is "write" or "read" action , and "op"(operation) is tagged with key "cache.op" usually + * This config term define which command should be converted to write Operation . + * + * @see org.apache.skywalking.apm.agent.core.context.tag.Tags#CACHE_OP + * @see RedisChannelWriterInterceptor#parseOperation(String) + */ + public static Set OPERATION_MAPPING_WRITE = new HashSet<>(Arrays.asList( + "getset", + "set", + "setbit", + "setex ", + "setnx ", + "setrange", + "strlen ", + "mset", + "msetnx ", + "psetex", + "incr ", + "incrby ", + "incrbyfloat", + "decr ", + "decrby ", + "append ", + "hmset", + "hset", + "hsetnx ", + "hincrby", + "hincrbyfloat", + "hdel", + "rpoplpush", + "rpush", + "rpushx", + "lpush", + "lpushx", + "lrem", + "ltrim", + "lset", + "brpoplpush", + "linsert", + "sadd", + "sdiff", + "sdiffstore", + "sinterstore", + "sismember", + "srem", + "sunion", + "sunionstore", + "sinter", + "zadd", + "zincrby", + "zinterstore", + "zrange", + "zrangebylex", + "zrangebyscore", + "zrank", + "zrem", + "zremrangebylex", + "zremrangebyrank", + "zremrangebyscore", + "zrevrange", + "zrevrangebyscore", + "zrevrank", + "zunionstore", + "xadd", + "xdel", + "del", + "xtrim" + )); + /** + * Operation represent a cache span is "write" or "read" action , and "op"(operation) is tagged with key "cache.op" usually + * This config term define which command should be converted to write Operation . + * + * @see org.apache.skywalking.apm.agent.core.context.tag.Tags#CACHE_OP + * @see RedisChannelWriterInterceptor#parseOperation(String) + */ + public static Set OPERATION_MAPPING_READ = new HashSet<>(Arrays.asList( + "getrange", + "getbit ", + "mget", + "hvals", + "hkeys", + "hlen", + "hexists", + "hget", + "hgetall", + "hmget", + "blpop", + "brpop", + "lindex", + "llen", + "lpop", + "lrange", + "rpop", + "scard", + "srandmember", + "spop", + "sscan", + "smove", + "zlexcount", + "zscore", + "zscan", + "zcard", + "zcount", + "xget", + "get", + "xread", + "xlen", + "xrange", + "xrevrange" + )); } } } diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptor.java b/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptor.java index 8d780d2bc..3e5c670e7 100644 --- a/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptor.java @@ -18,6 +18,7 @@ package org.apache.skywalking.apm.plugin.lettuce.v5; +import io.lettuce.core.codec.StringCodec; import io.lettuce.core.protocol.CommandArgs; import io.lettuce.core.protocol.DecoratedCommand; import io.lettuce.core.protocol.RedisCommand; @@ -33,16 +34,18 @@ import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.util.StringUtil; import java.lang.reflect.Method; +import java.nio.ByteBuffer; import java.util.Collection; import java.util.List; +import java.util.Optional; @SuppressWarnings("unchecked") public class RedisChannelWriterInterceptor implements InstanceMethodsAroundInterceptor { private static final String PASSWORD_MASK = "******"; private static final String ABBR = "..."; - private static final String DELIMITER_SPACE = " "; private static final String AUTH = "AUTH"; + private static final StringCodec STRING_CODEC = new StringCodec(); @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) { @@ -63,45 +66,48 @@ public class RedisChannelWriterInterceptor implements InstanceMethodsAroundInter return; } - StringBuilder dbStatement = new StringBuilder(); String operationName = "Lettuce/"; + String key = Constants.EMPTY_STRING; + String command = Constants.EMPTY_STRING; if (allArguments[0] instanceof RedisCommand) { RedisCommand redisCommand = (RedisCommand) allArguments[0]; - String command = redisCommand.getType().name(); + command = redisCommand.getType().name(); operationName = operationName + command; - dbStatement.append(command); if (LettucePluginConfig.Plugin.Lettuce.TRACE_REDIS_PARAMETERS) { - dbStatement.append(DELIMITER_SPACE).append(getArgsStatement(redisCommand)); + key = getArgsKey(redisCommand); } } else if (allArguments[0] instanceof Collection) { - Collection> redisCommands = (Collection>) allArguments[0]; operationName = operationName + "BATCH_WRITE"; - for (RedisCommand redisCommand : redisCommands) { - dbStatement.append(redisCommand.getType().name()).append(";"); - } + command = "BATCH_WRITE"; } AbstractSpan span = ContextManager.createExitSpan(operationName, peer); span.setComponent(ComponentsDefine.LETTUCE); - Tags.DB_TYPE.set(span, "Redis"); - Tags.DB_STATEMENT.set(span, dbStatement.toString()); + Tags.CACHE_TYPE.set(span, "Redis"); + if (StringUtil.isNotEmpty(key)) { + Tags.CACHE_KEY.set(span, key); + } + Tags.CACHE_CMD.set(span, command); + parseOperation(command.toLowerCase()).ifPresent(op -> Tags.CACHE_OP.set(span, op)); SpanLayer.asCache(span); span.prepareForAsync(); ContextManager.stopSpan(); enhancedCommand.setSkyWalkingDynamicField(span); } - private String getArgsStatement(RedisCommand redisCommand) { - String statement; + private String getArgsKey(RedisCommand redisCommand) { if (AUTH.equalsIgnoreCase(redisCommand.getType().name())) { - statement = PASSWORD_MASK; - } else { - CommandArgs args = redisCommand.getArgs(); - statement = (args != null) ? args.toCommandString() : Constants.EMPTY_STRING; + return PASSWORD_MASK; } - if (StringUtil.isNotEmpty(statement) && statement.length() > LettucePluginConfig.Plugin.Lettuce.REDIS_PARAMETER_MAX_LENGTH) { - statement = statement.substring(0, LettucePluginConfig.Plugin.Lettuce.REDIS_PARAMETER_MAX_LENGTH) + ABBR; + CommandArgs args = redisCommand.getArgs(); + if (args == null) { + return Constants.EMPTY_STRING; + } + ByteBuffer firstEncodedKey = args.getFirstEncodedKey(); + String key = STRING_CODEC.decodeKey(firstEncodedKey); + if (StringUtil.isNotEmpty(key) && key.length() > LettucePluginConfig.Plugin.Lettuce.REDIS_PARAMETER_MAX_LENGTH) { + key = StringUtil.cut(key, LettucePluginConfig.Plugin.Lettuce.REDIS_PARAMETER_MAX_LENGTH) + ABBR; } - return statement; + return key; } @Override @@ -144,4 +150,14 @@ public class RedisChannelWriterInterceptor implements InstanceMethodsAroundInter } return command; } + + private Optional parseOperation(String cmd) { + if (LettucePluginConfig.Plugin.Lettuce.OPERATION_MAPPING_READ.contains(cmd)) { + return Optional.of("read"); + } + if (LettucePluginConfig.Plugin.Lettuce.OPERATION_MAPPING_WRITE.contains(cmd)) { + return Optional.of("write"); + } + return Optional.empty(); + } } diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptorTest.java index 95d50afec..72cd0b3d5 100644 --- a/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptorTest.java @@ -121,7 +121,7 @@ public class RedisChannelWriterInterceptorTest { @Test public void testInterceptor() { - CommandArgs args = new CommandArgs<>(new ByteArrayCodec()).addKey("name".getBytes()).addValue("Tom".getBytes()); + CommandArgs args = new CommandArgs<>(new ByteArrayCodec()).addKey("name".getBytes()); MockRedisCommand redisCommand = new MockRedisCommand<>(CommandType.SET, null, args); interceptor.beforeMethod(mockRedisChannelWriterInstance, null, new Object[]{redisCommand}, null, null); interceptor.afterMethod(mockRedisChannelWriterInstance, null, null, null, null); @@ -136,7 +136,6 @@ public class RedisChannelWriterInterceptorTest { assertThat(SpanHelper.getComponentId(spans.get(0)), is(57)); List tags = SpanHelper.getTags(spans.get(0)); assertThat(tags.get(0).getValue(), is("Redis")); - assertThat(tags.get(1).getValue(), CoreMatchers.containsString("Tom")); assertThat(SpanHelper.getLayer(spans.get(0)), CoreMatchers.is(SpanLayer.CACHE)); assertThat(SpanHelper.getPeer(spans.get(0)), is(PEER)); } diff --git a/apm-sniffer/config/agent.config b/apm-sniffer/config/agent.config index 2e7bc4916..a3a335f7b 100755 --- a/apm-sniffer/config/agent.config +++ b/apm-sniffer/config/agent.config @@ -269,6 +269,10 @@ plugin.toolkit.log.transmit_formatted=${SW_PLUGIN_TOOLKIT_LOG_TRANSMIT_FORMATTED plugin.lettuce.trace_redis_parameters=${SW_PLUGIN_LETTUCE_TRACE_REDIS_PARAMETERS:false} # If set to positive number and `plugin.lettuce.trace_redis_parameters` is set to `true`, Redis command parameters would be collected and truncated to this length. plugin.lettuce.redis_parameter_max_length=${SW_PLUGIN_LETTUCE_REDIS_PARAMETER_MAX_LENGTH:128} +# Specify which command should be converted to write operation +plugin.lettuce.operation_mapping_write=${SW_PLUGIN_LETTUCE_OPERATION_MAPPING_WRITE:getset,set,setbit,setex,setnx,setrange,strlen,mset,msetnx,psetex,incr,incrby,incrbyfloat,decr,decrby,append,hmset,hset,hsetnx,hincrby,hincrbyfloat,hdel,rpoplpush,rpush,rpushx,lpush,lpushx,lrem,ltrim,lset,brpoplpush,linsert,sadd,sdiff,sdiffstore,sinterstore,sismember,srem,sunion,sunionstore,sinter,zadd,zincrby,zinterstore,zrange,zrangebylex,zrangebyscore,zrank,zrem,zremrangebylex,zremrangebyrank,zremrangebyscore,zrevrange,zrevrangebyscore,zrevrank,zunionstore,xadd,xdel,del,xtrim} +# Specify which command should be converted to read operation +plugin.lettuce.operation_mapping_read=${SW_PLUGIN_LETTUCE_OPERATION_MAPPING_READ:getrange,getbit,mget,hvals,hkeys,hlen,hexists,hget,hgetall,hmget,blpop,brpop,lindex,llen,lpop,lrange,rpop,scard,srandmember,spop,sscan,smove,zlexcount,zscore,zscan,zcard,zcount,xget,get,xread,xlen,xrange,xrevrange} # If set to true, the parameters of the cypher would be collected. plugin.neo4j.trace_cypher_parameters=${SW_PLUGIN_NEO4J_TRACE_CYPHER_PARAMETERS:false} # If set to positive number, the `db.cypher.parameters` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem. diff --git a/docs/en/setup/service-agent/java-agent/configurations.md b/docs/en/setup/service-agent/java-agent/configurations.md index 9669e9398..8aafa5390 100644 --- a/docs/en/setup/service-agent/java-agent/configurations.md +++ b/docs/en/setup/service-agent/java-agent/configurations.md @@ -106,6 +106,8 @@ This is the properties list supported in `agent/config/agent.config`. | `plugin.toolkit.log.transmit_formatted` | Whether or not to transmit logged data as formatted or un-formatted. | SW_PLUGIN_TOOLKIT_LOG_TRANSMIT_FORMATTED | `true` | | `plugin.lettuce.trace_redis_parameters` | If set to true, the parameters of Redis commands would be collected by Lettuce agent. | SW_PLUGIN_LETTUCE_TRACE_REDIS_PARAMETERS | `false` | | `plugin.lettuce.redis_parameter_max_length` | If set to positive number and `plugin.lettuce.trace_redis_parameters` is set to `true`, Redis command parameters would be collected and truncated to this length. | SW_PLUGIN_LETTUCE_REDIS_PARAMETER_MAX_LENGTH | `128` | +| `plugin.lettuce.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_LETTUCE_OPERATION_MAPPING_WRITE | | +| `plugin.lettuce.operation_mapping_read ` | Specify which command should be converted to `read` operation | SW_PLUGIN_LETTUCE_OPERATION_MAPPING_READ | Referenc [Lettuce-5.x-plugin](https://github.com/apache/skywalking-java/blob/main/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/LettucePluginConfig.java) | | `plugin.jedis.trace_redis_parameters` | If set to true, the parameters of Redis commands would be collected by Jedis agent. | SW_PLUGIN_JEDIS_TRACE_REDIS_PARAMETERS | `false` | | `plugin.jedis.redis_parameter_max_length` | If set to positive number and `plugin.jedis.trace_redis_parameters` is set to `true`, Redis command parameters would be collected and truncated to this length. | SW_PLUGIN_JEDIS_REDIS_PARAMETER_MAX_LENGTH | `128` | | `plugin.jedis.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_JEDIS_OPERATION_MAPPING_WRITE | | diff --git a/test/plugin/scenarios/lettuce-scenario/config/expectedData.yaml b/test/plugin/scenarios/lettuce-scenario/config/expectedData.yaml index 2b0ada8f8..43db79287 100644 --- a/test/plugin/scenarios/lettuce-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/lettuce-scenario/config/expectedData.yaml @@ -48,8 +48,10 @@ segmentItems: peer: not null skipAnalysis: false tags: - - {key: db.type, value: Redis} - - {key: db.statement, value: GET key} + - {key: cache.type, value: Redis} + - {key: cache.key, value: key} + - {key: cache.cmd, value: GET} + - {key: cache.op, value: read} - operationName: Lettuce/SET parentSpanId: 0 spanId: 2 @@ -62,8 +64,10 @@ segmentItems: peer: not null skipAnalysis: false tags: - - {key: db.type, value: Redis} - - {key: db.statement, value: SET key value} + - { key: cache.type, value: Redis } + - { key: cache.key, value: key0 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } - operationName: Lettuce/SET parentSpanId: 0 spanId: 3 @@ -76,8 +80,10 @@ segmentItems: peer: not null skipAnalysis: false tags: - - {key: db.type, value: Redis} - - {key: db.statement, value: SET key value} + - { key: cache.type, value: Redis } + - { key: cache.key, value: key1 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } - operationName: GET:/lettuce-scenario/case/lettuce-case parentSpanId: -1 spanId: 0