Enhance lettuce plugin to adopt uniform tags (#441)

This commit is contained in:
xzyJavaX 2023-01-16 11:46:39 +08:00 committed by GitHub
parent 8c989b74e9
commit f366503d58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 172 additions and 29 deletions

View File

@ -5,7 +5,7 @@ Release Notes.
8.15.0
------------------
* Enhance lettuce plugin to adopt uniform tags.
#### Documentation

View File

@ -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<String> 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<String> 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"
));
}
}
}

View File

@ -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<RedisCommand<?, ?, ?>> redisCommands = (Collection<RedisCommand<?, ?, ?>>) 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<String> 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();
}
}

View File

@ -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<TagValuePair> 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));
}

View File

@ -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.

View File

@ -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 | |

View File

@ -48,8 +48,10 @@ segmentItems:
peer: not null
skipAnalysis: false
tags:
- {key: db.type, value: Redis}
- {key: db.statement, value: GET key<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<key0> value<value0>}
- { 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<key1> value<value1>}
- { 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