Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions instrumentation/lettuce-6.5/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
apply plugin: 'java'

dependencies {
implementation(project(":agent-bridge"))
implementation group: 'io.lettuce', name: 'lettuce-core', version: '6.5.0.RELEASE'

testImplementation('org.testcontainers:testcontainers:1.17.1')
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'

testCompileOnly 'junit:junit:4.13.1'

testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}

jar {
manifest {
attributes 'Implementation-Title': 'com.newrelic.instrumentation.lettuce-6.5'
}
}

verifyInstrumentation {
passesOnly 'io.lettuce:lettuce-core:[6.5.0.RELEASE,)'
excludeRegex '.*RC.*'
}

site {
title 'Lettuce 6.5'
type 'Framework'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
*
* * Copyright 2025 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package com.nr.lettuce65.instrumentation;

import com.newrelic.api.agent.ExternalParameters;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Segment;

import java.util.function.BiConsumer;

public class NRBiConsumer<T> implements BiConsumer<T, Throwable> {

private Segment segment = null;
private ExternalParameters params = null;

public NRBiConsumer(Segment s, ExternalParameters p) {
segment = s;
params = p;
}

@Override
public void accept(T t, Throwable u) {
if (u != null) {
NewRelic.noticeError(u);
}

if (segment != null) {
if (params != null) {
segment.reportAsExternal(params);
}
segment.end();
segment = null;
} else {
if (params != null) {
NewRelic.getAgent().getTracedMethod().reportAsExternal(params);
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
*
* * Copyright 2025 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package com.nr.lettuce65.instrumentation;

import com.newrelic.api.agent.NewRelic;

import java.util.function.Consumer;

public class NRErrorConsumer implements Consumer<Throwable> {

private NRHolder holder = null;

public NRErrorConsumer(NRHolder h) {
holder = h;
}

@Override
public void accept(Throwable t) {
NewRelic.noticeError(t);
if (holder != null && !holder.hasEnded()) {
holder.end();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
*
* * Copyright 2025 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package com.nr.lettuce65.instrumentation;

import com.newrelic.api.agent.DatastoreParameters;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Segment;
import com.newrelic.api.agent.Token;

import java.util.logging.Level;

public class NRHolder {
private String segmentName = null;
private Segment segment = null;
private DatastoreParameters params = null;
private Token token = null;
private boolean hasEnded = false;

public NRHolder(Segment s, DatastoreParameters p) {
segment = s;
params = p;
token = NewRelic.getAgent().getTransaction().getToken();
hasEnded = false;
}

public NRHolder(String s, DatastoreParameters p) {
segmentName = s;
params = p;
token = NewRelic.getAgent().getTransaction().getToken();
hasEnded = false;
}

public void startSegment() {
segment = NewRelic.getAgent().getTransaction().startSegment(segmentName);
}

public boolean hasEnded() {
return hasEnded;
}

public void end() {
if (token != null) {
token.linkAndExpire();
token = null;
}
if (segment != null && params != null) {
String operation = params.getOperation();
if (operation.equalsIgnoreCase("expire")) {
segment.ignore();
} else {
segment.reportAsExternal(params);
segment.end();
}
params = null;
segment = null;
} else {
if (params != null) {
String operation = params.getOperation();
if (!operation.equalsIgnoreCase("expire")) {
NewRelic.getAgent().getTracedMethod().reportAsExternal(params);
}
} else {
NewRelic.getAgent().getLogger().log(Level.WARNING, "DatastoreParameters was null for Lettuce instrumentation. Unable to report as external");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
*
* * Copyright 2025 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package com.nr.lettuce65.instrumentation;

import reactor.core.publisher.SignalType;

import java.util.function.Consumer;

public class NRSignalTypeConsumer implements Consumer<SignalType> {

private NRHolder holder = null;

public NRSignalTypeConsumer(NRHolder h) {
holder = h;
}

@Override
public void accept(SignalType t) {
if (holder != null && !holder.hasEnded()) {
holder.end();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
*
* * Copyright 2025 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package com.nr.lettuce65.instrumentation;

import org.reactivestreams.Subscription;

import java.util.function.Consumer;

public class NRSubscribeConsumer implements Consumer<Subscription> {

private NRHolder holder = null;

public NRSubscribeConsumer(NRHolder h) {
holder = h;
}

@Override
public void accept(Subscription t) {
if (holder != null) {
holder.startSegment();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
*
* * Copyright 2025 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.nr.lettuce65.instrumentation;

import com.newrelic.api.agent.DatastoreParameters;
import io.lettuce.core.RedisURI;

public class RedisDatastoreParameters {
public static DatastoreParameters from(RedisURI uri, String operation) {
DatastoreParameters params;
if (uri != null) {
params = DatastoreParameters.product("Redis").collection(null).operation(operation)
.instance(uri.getHost(), uri.getPort()).databaseName(String.valueOf(uri.getDatabase())).build();
} else {
params = DatastoreParameters.product("Redis").collection(null).operation(operation).noInstance()
.noDatabaseName().noSlowQuery().build();
}
return params;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
*
* * Copyright 2025 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package io.lettuce.core;

import com.newrelic.api.agent.DatastoreParameters;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Segment;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import com.nr.lettuce65.instrumentation.NRBiConsumer;
import com.nr.lettuce65.instrumentation.RedisDatastoreParameters;
import io.lettuce.core.api.StatefulConnection;
import io.lettuce.core.protocol.AsyncCommand;
import io.lettuce.core.protocol.ProtocolKeyword;
import io.lettuce.core.protocol.RedisCommand;

@Weave(originalName = "io.lettuce.core.AbstractRedisAsyncCommands")
public abstract class AbstractRedisAsyncCommands_Instrumentation<K, V> {

public abstract StatefulConnection<K, V> getConnection();

@SuppressWarnings("unchecked")
@Trace
public <T> AsyncCommand<K, V, T> dispatch(RedisCommand<K, V, T> cmd) {
AsyncCommand<K, V, T> acmd = Weaver.callOriginal();
RedisURI uri = null;

StatefulConnection<K, V> conn = getConnection();
if (StatefulRedisConnectionImpl_Instrumentation.class.isInstance(conn)) {
StatefulRedisConnectionImpl_Instrumentation<K, V> connImpl = (StatefulRedisConnectionImpl_Instrumentation<K, V>) conn;
if (connImpl.redisURI != null) {
uri = connImpl.redisURI;
}
}
String operation = "UnknownOp";
ProtocolKeyword t = cmd.getType();
if (t != null && t.toString() != null && !t.toString().isEmpty()) {
operation = t.toString();
}
if (operation.equalsIgnoreCase("expire")) {
return acmd;
}
DatastoreParameters params = RedisDatastoreParameters.from(uri, operation);
Segment segment = NewRelic.getAgent().getTransaction().startSegment("Redis", operation);
NRBiConsumer<T> nrBiConsumer = new NRBiConsumer<T>(segment, params);
acmd.whenComplete(nrBiConsumer);
return acmd;
}

}
Loading
Loading