Skip to content

Commit 66d1f13

Browse files
Merge pull request #2250 from newrelic/crac_testing
Implement support for CRaC
2 parents 875b6e3 + 459085b commit 66d1f13

File tree

6 files changed

+159
-29
lines changed

6 files changed

+159
-29
lines changed

newrelic-agent/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ dependencies {
101101

102102
shadowIntoJar("org.yaml:snakeyaml:1.33")
103103

104+
shadowIntoJar("org.crac:crac:1.5.0")
105+
104106
implementation("javax.management.j2ee:management-api:1.1-rev-1") {
105107
transitive = false
106108
}

newrelic-agent/src/main/java/com/newrelic/agent/MetricNames.java

+3
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,9 @@ public class MetricNames {
416416

417417
public static final String SUPPORTABILITY_AGENT_CONNECT_BACKOFF_ATTEMPTS = "Supportability/Agent/Collector/Connect/BackoffAttempts";
418418

419+
public static final String SUPPORTABILITY_AGENT_CRAC_CHECKPOINT = "Supportability/Agent/CracCheckpoint";
420+
public static final String SUPPORTABILITY_AGENT_CRAC_RESTORE = "Supportability/Agent/CracRestore";
421+
419422
// expected errors
420423
public static final String SUPPORTABILITY_API_EXPECTED_ERROR_API_MESSAGE = "ExpectedError/Api/Message";
421424
public static final String SUPPORTABILITY_API_EXPECTED_ERROR_API_THROWABLE = "ExpectedError/Api/Throwable";

newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/context/FinalClassTransformer.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,17 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
145145
}
146146

147147
private void writeClassFiles(String className, InstrumentationContext context, byte[] classBytes) {
148+
PrintWriter oldFileWriter = null;
149+
PrintWriter newFileWriter = null;
148150
try {
149151
File old = File.createTempFile(className.replace('/', '_'), ".old", BootstrapLoader.getTempDir());
150-
Utils.print(context.bytes, new PrintWriter(old));
151-
152+
oldFileWriter = new PrintWriter(old);
153+
Utils.print(context.bytes, oldFileWriter);
152154
Agent.LOG.debug("Wrote " + old.getAbsolutePath());
153-
File newFile = File.createTempFile(className.replace('/', '_'), ".new", BootstrapLoader.getTempDir());
154155

155-
Utils.print(classBytes, new PrintWriter(newFile));
156+
File newFile = File.createTempFile(className.replace('/', '_'), ".new", BootstrapLoader.getTempDir());
157+
newFileWriter = new PrintWriter(newFile);
158+
Utils.print(classBytes, newFileWriter);
156159
Agent.LOG.debug("Wrote " + newFile.getAbsolutePath());
157160

158161
File newClassFile = File.createTempFile(className.replace('/', '_'), ".new.class", BootstrapLoader.getTempDir());
@@ -162,6 +165,9 @@ private void writeClassFiles(String className, InstrumentationContext context, b
162165
Agent.LOG.debug("Wrote " + newClassFile.getAbsolutePath());
163166
} catch (Throwable t) {
164167
Agent.LOG.log(Level.FINEST, t, "Error writing debug bytecode for {0}", className);
168+
} finally {
169+
if (oldFileWriter != null) oldFileWriter.close();
170+
if (newFileWriter != null) newFileWriter.close();
165171
}
166172
}
167173

newrelic-agent/src/main/java/com/newrelic/agent/logging/Log4jLogManager.java

+49-21
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@
1111
import com.newrelic.agent.config.AgentConfig;
1212
import com.newrelic.agent.config.AgentConfigImpl;
1313
import com.newrelic.agent.config.AgentJarHelper;
14+
import com.newrelic.api.agent.NewRelic;
1415
import org.apache.logging.log4j.Level;
16+
import org.apache.logging.log4j.core.config.Configurator;
17+
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
18+
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
19+
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
1520

1621
import java.io.File;
22+
import java.net.MalformedURLException;
1723
import java.net.URL;
1824
import java.text.MessageFormat;
1925
import java.util.HashMap;
@@ -78,27 +84,6 @@ private Log4jLogger initializeRootLogger(String name) {
7884
// and we want to clear any other log4j system properties
7985
clearAllLog4jSystemProperties(systemProps);
8086

81-
URL log4jConfigXmlUrl = null;
82-
if (jarFileName.endsWith(".jar")) {
83-
// it isn't enough to specify the jar, we have to specify the path within the jar
84-
log4jConfigXmlUrl = new URL(new StringBuilder("jar:file:")
85-
.append(jarFileName)
86-
.append("!")
87-
.append(AGENT_JAR_LOG4J_CONFIG_FILE)
88-
.toString());
89-
} else {
90-
// we likely have received a path to a set of class files (this happens when running tests)
91-
try {
92-
// guava's Resources class is usually smart enough to figure out where to find the log4j2.xml file
93-
log4jConfigXmlUrl = Resources.getResource(this.getClass(), AGENT_JAR_LOG4J_CONFIG_FILE);
94-
} catch (IllegalArgumentException iae) {
95-
// fallback on path
96-
log4jConfigXmlUrl = new File(jarFileName).toURI().toURL();
97-
}
98-
}
99-
100-
System.setProperty(CONFIG_FILE_PROP, log4jConfigXmlUrl.toString());
101-
System.setProperty(LEGACY_CONFIG_FILE_PROP, log4jConfigXmlUrl.toString());
10287
// Log4j won't be able to find log4j-provider.properties because it isn't on the classpath (it's in our agent) so this sets it manually
10388
System.setProperty(CONTEXT_FACTORY_PROP, "org.apache.logging.log4j.core.impl.Log4jContextFactory");
10489

@@ -111,6 +96,16 @@ private Log4jLogger initializeRootLogger(String name) {
11196
System.setProperty(LEGACY_CLASSLOADER_PROP, "true");
11297
System.setProperty(CLASSLOADER_PROP, "true");
11398

99+
// This is in place in case we need to revert the programmatic initialization of our logger to
100+
// use the log4j XML config file. This has to use a system property or env var because the config
101+
// service is not properly bootstrapped yet.
102+
if (Boolean.getBoolean("newrelic.config.agent_root_logger_init_with_file") ||
103+
Boolean.parseBoolean(System.getenv("NEW_RELIC_AGENT_ROOT_LOGGER_INIT_WITH_FILE"))) {
104+
initLog4jViaFile(jarFileName);
105+
} else {
106+
initLog4jViaConfigurationBuilder();
107+
}
108+
114109
try {
115110
logger = createRootLogger(name);
116111
} finally {
@@ -275,6 +270,39 @@ private boolean canWriteLogFile(String logFileName) {
275270
}
276271
}
277272

273+
private void initLog4jViaConfigurationBuilder() {
274+
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
275+
builder.setConfigurationName("Default");
276+
builder.add(builder.newRootLogger(Level.INFO));
277+
builder.setShutdownHook("disable");
278+
builder.add(builder.newLogger("com.newrelic.agent.deps.org.reflections", Level.OFF));
279+
Configurator.initialize(builder.build());
280+
}
281+
282+
private void initLog4jViaFile(String jarFileName) throws MalformedURLException {
283+
URL log4jConfigXmlUrl = null;
284+
if (jarFileName.endsWith(".jar")) {
285+
// it isn't enough to specify the jar, we have to specify the path within the jar
286+
log4jConfigXmlUrl = new URL(new StringBuilder("jar:file:")
287+
.append(jarFileName)
288+
.append("!")
289+
.append(AGENT_JAR_LOG4J_CONFIG_FILE)
290+
.toString());
291+
} else {
292+
// we likely have received a path to a set of class files (this happens when running tests)
293+
try {
294+
// guava's Resources class is usually smart enough to figure out where to find the log4j2.xml file
295+
log4jConfigXmlUrl = Resources.getResource(this.getClass(), AGENT_JAR_LOG4J_CONFIG_FILE);
296+
} catch (IllegalArgumentException iae) {
297+
// fallback on path
298+
log4jConfigXmlUrl = new File(jarFileName).toURI().toURL();
299+
}
300+
}
301+
302+
System.setProperty(CONFIG_FILE_PROP, log4jConfigXmlUrl.toString());
303+
System.setProperty(LEGACY_CONFIG_FILE_PROP, log4jConfigXmlUrl.toString());
304+
}
305+
278306
@Override
279307
public void addConsoleHandler() {
280308
rootLogger.addConsoleAppender();

newrelic-agent/src/main/java/com/newrelic/agent/logging/Log4jLogger.java

+64-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import org.apache.logging.log4j.core.layout.PatternLayout;
2121
import org.apache.logging.log4j.message.Message;
2222
import org.apache.logging.log4j.message.ParameterizedMessage;
23+
import org.crac.Context;
24+
import org.crac.Core;
25+
import org.crac.Resource;
2326

2427
import java.security.AccessController;
2528
import java.security.PrivilegedAction;
@@ -30,7 +33,7 @@
3033

3134
import static com.newrelic.agent.logging.FileAppenderFactory.FILE_APPENDER_NAME;
3235

33-
class Log4jLogger implements IAgentLogger {
36+
class Log4jLogger implements IAgentLogger, Resource {
3437

3538
/**
3639
* The name of the console appender.
@@ -48,6 +51,12 @@ class Log4jLogger implements IAgentLogger {
4851
private final Logger logger;
4952
private final Map<String, IAgentLogger> childLoggers = new ConcurrentHashMap<>();
5053

54+
private String fileName;
55+
private long logLimitBytes;
56+
private int fileCount;
57+
private boolean isDaily;
58+
private String path;
59+
5160
/**
5261
* Creates this Log4jLogger.
5362
*
@@ -57,6 +66,7 @@ private Log4jLogger(final String name, boolean isAgentRoot) {
5766
logger = LogManager.getLogger(name);
5867

5968
if (isAgentRoot) {
69+
Core.getGlobalContext().register(this);
6070
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
6171
Configuration config = ctx.getConfiguration();
6272
LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
@@ -251,6 +261,41 @@ public void addConsoleAppender() {
251261
ctx.updateLoggers();
252262
}
253263

264+
/**
265+
* Stops the file appender for the taking of a checkpoint
266+
*/
267+
public void stopFileAppender() {
268+
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
269+
Configuration config = ctx.getConfiguration();
270+
LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
271+
272+
Appender checkpointFileAppender = loggerConfig.getAppenders().get(FILE_APPENDER_NAME);
273+
if (checkpointFileAppender != null) {
274+
// remove it from the list, so we don't try to write to it any longer
275+
// this could cause missed messages in the log file, but would still
276+
// go to the console
277+
loggerConfig.removeAppender(checkpointFileAppender.getName());
278+
// stop to close the open file
279+
checkpointFileAppender.stop();
280+
ctx.updateLoggers();
281+
}
282+
}
283+
284+
/**
285+
* Starts the file appender after a restore
286+
*/
287+
public void startFileAppender() {
288+
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
289+
Configuration config = ctx.getConfiguration();
290+
LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
291+
292+
Appender checkpointFileAppender = loggerConfig.getAppenders().get(FILE_APPENDER_NAME);
293+
if (checkpointFileAppender == null && fileName != null) { // don't add it if it's already there
294+
addFileAppender(fileName, logLimitBytes, fileCount, isDaily, path);
295+
ctx.updateLoggers();
296+
}
297+
}
298+
254299
/**
255300
* Adds a file appender.
256301
*
@@ -259,6 +304,12 @@ public void addConsoleAppender() {
259304
* @param fileCount The number of files.
260305
*/
261306
public void addFileAppender(String fileName, long logLimitBytes, int fileCount, boolean isDaily, String path) {
307+
this.fileName = fileName;
308+
this.logLimitBytes = logLimitBytes;
309+
this.fileCount = fileCount;
310+
this.isDaily = isDaily;
311+
this.path = path;
312+
262313
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
263314
Configuration config = ctx.getConfiguration();
264315
LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
@@ -479,4 +530,16 @@ public void logToChild(String childName, Level level, String pattern, Object par
479530
}
480531
}
481532

533+
@Override
534+
public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
535+
Agent.LOG.info("Stopping Log4jLogger for CRaC checkpoint, log messages may be missing from the log file between here and restore, but should still appear in the console");
536+
stopFileAppender();
537+
}
538+
539+
@Override
540+
public void afterRestore(Context<? extends Resource> context) throws Exception {
541+
Agent.LOG.info("Restarting Log4jLogger for CRaC restore");
542+
startFileAppender();
543+
}
544+
482545
}

newrelic-agent/src/main/java/com/newrelic/agent/transport/apache/ApacheHttpClientWrapper.java

+31-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import com.newrelic.agent.transport.HostConnectException;
1515
import com.newrelic.agent.transport.HttpClientWrapper;
1616
import com.newrelic.agent.transport.ReadResult;
17+
import com.newrelic.api.agent.NewRelic;
1718
import org.apache.http.Header;
1819
import org.apache.http.HttpEntity;
1920
import org.apache.http.HttpResponse;
@@ -37,6 +38,9 @@
3738
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
3839
import org.apache.http.message.BasicHeader;
3940
import org.apache.http.protocol.HttpContext;
41+
import org.crac.Context;
42+
import org.crac.Core;
43+
import org.crac.Resource;
4044

4145
import javax.net.ssl.SSLContext;
4246
import java.io.BufferedReader;
@@ -54,15 +58,22 @@
5458

5559
import static com.newrelic.agent.transport.DataSenderImpl.GZIP_ENCODING;
5660

57-
public class ApacheHttpClientWrapper implements HttpClientWrapper {
61+
public class ApacheHttpClientWrapper implements HttpClientWrapper, Resource {
5862
private final ApacheProxyManager proxyManager;
59-
private final PoolingHttpClientConnectionManager connectionManager;
60-
private final CloseableHttpClient httpClient;
63+
private PoolingHttpClientConnectionManager connectionManager;
64+
private CloseableHttpClient httpClient;
65+
private SSLContext sslContext;
66+
private final int defaultTimeoutInMillis;
6167

6268
public ApacheHttpClientWrapper(ApacheProxyManager proxyManager, SSLContext sslContext, int defaultTimeoutInMillis) {
6369
this.proxyManager = proxyManager;
6470
this.connectionManager = createHttpClientConnectionManager(sslContext);
6571
this.httpClient = createHttpClient(defaultTimeoutInMillis);
72+
73+
this.sslContext = sslContext;
74+
this.defaultTimeoutInMillis = defaultTimeoutInMillis;
75+
76+
Core.getGlobalContext().register(this);
6677
}
6778

6879
private static final String USER_AGENT_HEADER_VALUE = initUserHeaderValue();
@@ -259,4 +270,21 @@ private BufferedReader getBufferedReader(HttpResponse response, InputStream is)
259270
}
260271
return new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
261272
}
273+
274+
@Override
275+
public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
276+
Agent.LOG.info("Stopping collector connection for CRaC checkpoint");
277+
NewRelic.getAgent().getMetricAggregator().incrementCounter(MetricNames.SUPPORTABILITY_AGENT_CRAC_CHECKPOINT);
278+
connectionManager.close();
279+
httpClient.close();
280+
}
281+
282+
@Override
283+
public void afterRestore(Context<? extends Resource> context) throws Exception {
284+
Agent.LOG.info("Restarting collector connection for CRaC restore");
285+
NewRelic.getAgent().getMetricAggregator().incrementCounter(MetricNames.SUPPORTABILITY_AGENT_CRAC_RESTORE);
286+
connectionManager = createHttpClientConnectionManager(sslContext);
287+
httpClient = createHttpClient(defaultTimeoutInMillis);
288+
}
289+
262290
}

0 commit comments

Comments
 (0)