Skip to content

Commit

Permalink
TS-31571 First approach with an asynchronous logger that sends logs t…
Browse files Browse the repository at this point in the history
…o Teamscale
  • Loading branch information
stahlbauer committed Jul 29, 2024
1 parent a4a63ea commit 691af36
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 5 deletions.
9 changes: 9 additions & 0 deletions agent/src/main/java/com/teamscale/jacoco/agent/PreMain.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.teamscale.jacoco.agent;

import com.teamscale.client.HttpUtils;
import com.teamscale.client.TeamscaleServer;
import com.teamscale.jacoco.agent.configuration.AgentOptionReceiveException;
import com.teamscale.jacoco.agent.logging.LogToTeamscaleAppender;
import com.teamscale.jacoco.agent.options.AgentOptionParseException;
import com.teamscale.jacoco.agent.options.AgentOptions;
import com.teamscale.jacoco.agent.options.AgentOptionsParser;
Expand All @@ -11,6 +13,7 @@
import com.teamscale.jacoco.agent.options.TeamscalePropertiesUtils;
import com.teamscale.jacoco.agent.testimpact.TestwiseCoverageAgent;
import com.teamscale.jacoco.agent.upload.UploaderException;
import com.teamscale.jacoco.agent.upload.teamscale.TeamscaleConfig;
import com.teamscale.jacoco.agent.util.AgentUtils;
import com.teamscale.jacoco.agent.logging.DebugLogDirectoryPropertyDefiner;
import com.teamscale.jacoco.agent.logging.LogDirectoryPropertyDefiner;
Expand All @@ -31,6 +34,8 @@
import java.util.List;
import java.util.Optional;

import static com.teamscale.jacoco.agent.logging.LoggingUtils.getLoggerContext;

/** Container class for the premain entry point for the agent. */
public class PreMain {

Expand Down Expand Up @@ -149,6 +154,10 @@ private static void initializeLogging(AgentOptions agentOptions, DelayedLogger l
loggingResources = LoggingUtils.initializeLogging(agentOptions.getLoggingConfig());
logger.info("Logging to " + new LogDirectoryPropertyDefiner().getPropertyValue());
}

if (agentOptions.getTeamscaleServerOptions().isConfiguredForServerConnection()) {
LogToTeamscaleAppender.addTeamscaleAppenderTo(getLoggerContext(), agentOptions);
}
}

/** Closes the opened logging contexts. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,7 @@ private void unregisterProfiler() {
}
}


public String getProfilerId() {
return profilerId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.teamscale.jacoco.agent.logging;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import com.teamscale.client.ProfilerLogEntry;
import com.teamscale.client.TeamscaleClient;
import com.teamscale.jacoco.agent.options.AgentOptions;
import retrofit2.Call;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class LogToTeamscaleAppender extends AppenderBase<ILoggingEvent> {

private String profilerId;
private TeamscaleClient teamscaleClient;
private int batchSize = 10;
private Duration flushInterval = Duration.ofSeconds(3);
private final List<ProfilerLogEntry> logBuffer = new ArrayList<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

public void setTeamscaleClient(TeamscaleClient teamscaleClient) {
this.teamscaleClient = teamscaleClient;
}

public void setProfilerId(String profilerId) {
this.profilerId = profilerId;
}

public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}

public void setFlushInterval(Duration flushInterval) {
this.flushInterval = flushInterval;
}

@Override
public void start() {
super.start();
scheduler.scheduleAtFixedRate(this::flush, flushInterval.toMillis(), flushInterval.toMillis(), TimeUnit.MILLISECONDS);
}

@Override
protected void append(ILoggingEvent eventObject) {
synchronized (logBuffer) {
logBuffer.add(formatLog(eventObject));
if (logBuffer.size() >= batchSize) {
flush();
}
}
}

private ProfilerLogEntry formatLog(ILoggingEvent eventObject) {
long timestamp = eventObject.getTimeStamp();
String message = eventObject.getFormattedMessage();
String severity = eventObject.getLevel().toString();
return new ProfilerLogEntry(timestamp, message, severity);
}

private void flush() {
List<ProfilerLogEntry> logsToSend;
synchronized (logBuffer) {
if (logBuffer.isEmpty()) {
return;
}
logsToSend = new ArrayList<>(logBuffer);
logBuffer.clear();
}
sendLogs(logsToSend);
}

private void sendLogs(List<ProfilerLogEntry> logs) {
CompletableFuture.runAsync(() -> {
try {
Call<Void> call = teamscaleClient.service.postProfilerLog(profilerId, logs);
retrofit2.Response<Void> response = call.execute();
if (!response.isSuccessful()) {
throw new RuntimeException("Failed to send log: HTTP error code : " + response.code());
}
} catch (Exception e) {
e.printStackTrace(); // Handle exceptions appropriately in production code
}
});
}

@Override
public void stop() {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
}
flush(); // Ensure remaining logs are sent
super.stop();
}


public static void addTeamscaleAppenderTo(LoggerContext context, AgentOptions agentOptions) {
LogToTeamscaleAppender logToTeamscaleAppender = new LogToTeamscaleAppender();
logToTeamscaleAppender.setContext(context);
logToTeamscaleAppender.setProfilerId(agentOptions.configurationViaTeamscale.getProfilerId());
logToTeamscaleAppender.setTeamscaleClient(agentOptions.createTeamscaleClient());
logToTeamscaleAppender.start();

Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.addAppender(logToTeamscaleAppender);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ public static LoggingResources initializeDefaultLogging() {
return new LoggingResources();
}

private static LoggerContext getLoggerContext() {
/**
* Returns the logger context.
*/
public static LoggerContext getLoggerContext() {
return (LoggerContext) LoggerFactory.getILoggerFactory();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@ public TeamscaleMockServer withImpactedTests(String... impactedTests) {
/** Configures the server to answer all impacted test calls with the given tests. */
public TeamscaleMockServer withProfilerConfiguration(ProfilerConfiguration profilerConfiguration) {
this.profilerConfiguration = profilerConfiguration;
service.post("api/v2024.7.0/running-profilers", this::handleProfilerRegistration);
service.put("api/v2024.7.0/running-profilers/:profilerId", this::handleProfilerHeartbeat);
service.delete("api/v2024.7.0/running-profilers/:profilerId", this::handleProfilerUnregister);
service.post("api/v2024.7.0/profilers", this::handleProfilerRegistration);
service.put("api/v2024.7.0/profilers/:profilerId", this::handleProfilerHeartbeat);
service.delete("api/v2024.7.0/profilers/:profilerId", this::handleProfilerUnregister);
service.post("api/v2024.7.0/profilers/:profilerId/logs", this::handleProfilerLogs);
return this;
}

Expand Down Expand Up @@ -146,6 +147,12 @@ private String handleProfilerHeartbeat(Request request, Response response) {
return "";
}

private String handleProfilerLogs(Request request, Response response) {
collectedUserAgents.add(request.headers("User-Agent"));
profilerEvents.add("Profiler " + request.params(":profilerId") + " sent logs");
return "";
}

private String handleProfilerUnregister(Request request, Response response) {
collectedUserAgents.add(request.headers("User-Agent"));
profilerEvents.add("Profiler " + request.params(":profilerId") + " unregistered");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ Call<ResponseBody> sendHeartbeat(
@DELETE("api/v2024.7.0/profilers/{profilerId}")
Call<ResponseBody> unregisterProfiler(@Path("profilerId") String profilerId);

@POST("api/v2024.7.0/profilers/{profilerId}/logs")
Call<Void> postProfilerLog(
@Path("profilerId") String profilerId,
@Body List<ProfilerLogEntry> logEntries
);

/**
* Uploads the given report body to Teamscale as blocking call with movetolastcommit set to false.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.teamscale.client;

public class ProfilerLogEntry {

private final long timestamp;

private final String message;

private final String severity;

public ProfilerLogEntry(long timestamp, String message, String severity) {
this.timestamp = timestamp;
this.message = message;
this.severity = severity;
}

}

0 comments on commit 691af36

Please sign in to comment.