generated from newrelic-experimental/java-instrumentation-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: first functional draft log4jAppender
- Loading branch information
1 parent
b9a65b3
commit 384774c
Showing
6 changed files
with
432 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
plugins { | ||
id 'java' | ||
id 'com.github.johnrengelman.shadow' version '6.1.0' | ||
} | ||
|
||
repositories { | ||
mavenCentral() // Ensure you are using Maven Central repository | ||
} | ||
|
||
dependencies { | ||
// OkHttp for HTTP requests | ||
implementation 'com.squareup.okhttp3:okhttp:4.9.3' | ||
|
||
// Jackson for JSON processing | ||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.1' | ||
|
||
// Log4j Core for logging | ||
implementation 'org.apache.logging.log4j:log4j-core:2.14.1' | ||
implementation 'org.apache.logging.log4j:log4j-api:2.14.1' | ||
} | ||
|
||
jar { | ||
manifest { | ||
attributes( | ||
'Implementation-Title': 'com.newrelic.labs.custom-log4j-appender', | ||
'Implementation-Vendor': 'New Relic Labs', | ||
'Implementation-Vendor-Id': 'com.newrelic.labs', | ||
'Implementation-Version': '1.0' | ||
) | ||
} | ||
} | ||
|
||
shadowJar { | ||
mergeServiceFiles() | ||
manifest { | ||
attributes 'Main-Class': 'com.newrelic.labs.logforwarder.MAIN' | ||
} | ||
} | ||
|
||
tasks.withType(JavaCompile) { | ||
options.encoding = 'UTF-8' | ||
} |
37 changes: 37 additions & 0 deletions
37
custom-log4j-appender/src/main/java/com/newrelic/labs/LogEntry.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.newrelic.labs; | ||
|
||
public class LogEntry { | ||
private final String message; | ||
private final String applicationName; | ||
private final String name; | ||
private final String logtype; | ||
private final long timestamp; | ||
|
||
public LogEntry(String message, String applicationName, String name, String logtype, long timestamp) { | ||
this.message = message; | ||
this.applicationName = applicationName; | ||
this.name = name; | ||
this.logtype = logtype; | ||
this.timestamp = timestamp; | ||
} | ||
|
||
public String getMessage() { | ||
return message; | ||
} | ||
|
||
public String getApplicationName() { | ||
return applicationName; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public String getLogType() { | ||
return logtype; | ||
} | ||
|
||
public long getTimestamp() { | ||
return timestamp; | ||
} | ||
} |
156 changes: 156 additions & 0 deletions
156
custom-log4j-appender/src/main/java/com/newrelic/labs/LogForwarder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package com.newrelic.labs; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.net.InetAddress; | ||
import java.net.UnknownHostException; | ||
import java.time.LocalDateTime; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.BlockingQueue; | ||
import java.util.zip.GZIPOutputStream; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
import okhttp3.MediaType; | ||
import okhttp3.OkHttpClient; | ||
import okhttp3.Request; | ||
import okhttp3.RequestBody; | ||
import okhttp3.Response; | ||
|
||
public class LogForwarder { | ||
private final BlockingQueue<LogEntry> logQueue; | ||
private final String apiKey; | ||
private final String apiURL; | ||
private final OkHttpClient client = new OkHttpClient(); | ||
private final ObjectMapper objectMapper = new ObjectMapper(); | ||
private final long maxMessageSize; | ||
|
||
public LogForwarder(String apiKey, String apiURL, long maxMessageSize, BlockingQueue<LogEntry> logQueue) { | ||
this.apiKey = apiKey; | ||
this.apiURL = apiURL; | ||
this.maxMessageSize = maxMessageSize; | ||
this.logQueue = logQueue; | ||
} | ||
|
||
public void addToQueue(List<String> lines, String applicationName, String name, String logtype) { | ||
for (String line : lines) { | ||
logQueue.add(new LogEntry(line, applicationName, name, logtype, System.currentTimeMillis())); | ||
} | ||
} | ||
|
||
public boolean isInitialized() { | ||
return apiKey != null && apiURL != null; | ||
} | ||
|
||
public void flush(List<LogEntry> logEntries) { | ||
InetAddress localhost = null; | ||
try { | ||
localhost = InetAddress.getLocalHost(); | ||
} catch (UnknownHostException e) { | ||
e.printStackTrace(); | ||
} | ||
|
||
String hostname = localhost != null ? localhost.getHostName() : "unknown"; | ||
|
||
@SuppressWarnings("unused") | ||
MediaType mediaType = MediaType.parse("application/json"); | ||
|
||
try { | ||
List<Map<String, Object>> logEvents = new ArrayList<>(); | ||
for (LogEntry entry : logEntries) { | ||
Map<String, Object> logEvent = objectMapper.convertValue(entry, LowercaseKeyMap.class); | ||
logEvent.put("hostname", hostname); | ||
logEvent.put("logtype", entry.getLogType()); | ||
logEvent.put("timestamp", entry.getTimestamp()); // Use log creation timestamp | ||
logEvent.put("applicationName", entry.getApplicationName()); | ||
logEvent.put("name", entry.getName()); | ||
logEvent.put("source", "NRBatchingAppender"); // Add custom field | ||
logEvents.add(logEvent); | ||
} | ||
|
||
String jsonPayload = objectMapper.writeValueAsString(logEvents); | ||
byte[] compressedPayload = gzipCompress(jsonPayload); | ||
|
||
if (compressedPayload.length > maxMessageSize) { // Configurable size limit | ||
System.err.println("Batch size exceeds limit, splitting batch..."); | ||
List<LogEntry> subBatch = new ArrayList<>(); | ||
int currentSize = 0; | ||
for (LogEntry entry : logEntries) { | ||
String entryJson = objectMapper.writeValueAsString(entry); | ||
int entrySize = gzipCompress(entryJson).length; | ||
if (currentSize + entrySize > maxMessageSize) { | ||
sendLogs(subBatch); | ||
subBatch.clear(); | ||
currentSize = 0; | ||
} | ||
subBatch.add(entry); | ||
currentSize += entrySize; | ||
} | ||
if (!subBatch.isEmpty()) { | ||
sendLogs(subBatch); | ||
} | ||
} else { | ||
sendLogs(logEntries); | ||
} | ||
} catch (IOException e) { | ||
System.err.println("Error during log forwarding: " + e.getMessage()); | ||
} | ||
} | ||
|
||
private void sendLogs(List<LogEntry> logEntries) throws IOException { | ||
InetAddress localhost = InetAddress.getLocalHost(); | ||
String hostname = localhost.getHostName(); | ||
|
||
List<Map<String, Object>> logEvents = new ArrayList<>(); | ||
for (LogEntry entry : logEntries) { | ||
Map<String, Object> logEvent = objectMapper.convertValue(entry, LowercaseKeyMap.class); | ||
logEvent.put("hostname", hostname); | ||
logEvent.put("logtype", entry.getLogType()); | ||
logEvent.put("applicationName", entry.getApplicationName()); | ||
logEvent.put("name", entry.getName()); | ||
logEvent.put("timestamp", entry.getTimestamp()); // Use log creation timestamp | ||
logEvent.put("source", "NRBatchingAppender"); // Add custom field | ||
logEvents.add(logEvent); | ||
} | ||
|
||
String jsonPayload = objectMapper.writeValueAsString(logEvents); | ||
byte[] compressedPayload = gzipCompress(jsonPayload); | ||
|
||
MediaType mediaType = MediaType.parse("application/json"); | ||
|
||
RequestBody requestBody = RequestBody.create(compressedPayload, mediaType); | ||
Request request = new Request.Builder().url(apiURL).post(requestBody).addHeader("X-License-Key", apiKey) | ||
.addHeader("Content-Type", "application/json").addHeader("Content-Encoding", "gzip").build(); | ||
|
||
try (Response response = client.newCall(request).execute()) { | ||
if (!response.isSuccessful()) { | ||
System.err.println("Failed to send logs to New Relic: " + response.code() + " - " + response.message()); | ||
System.err.println("Response body: " + response.body().string()); | ||
} else { | ||
LocalDateTime timestamp = LocalDateTime.now(); | ||
System.out.println("Logs sent to New Relic successfully: " + "at " + timestamp + " size: " | ||
+ compressedPayload.length + " Bytes"); | ||
System.out.println("Response: " + response.body().string()); | ||
} | ||
} | ||
} | ||
|
||
private byte[] gzipCompress(String input) throws IOException { | ||
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
try (GZIPOutputStream gzipOS = new GZIPOutputStream(bos)) { | ||
gzipOS.write(input.getBytes()); | ||
} | ||
return bos.toByteArray(); | ||
} | ||
|
||
public void close() { | ||
List<LogEntry> remainingLogs = new ArrayList<>(); | ||
logQueue.drainTo(remainingLogs); | ||
if (!remainingLogs.isEmpty()) { | ||
System.out.println("Flushing remaining " + remainingLogs.size() + " log events to New Relic..."); | ||
flush(remainingLogs); | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
custom-log4j-appender/src/main/java/com/newrelic/labs/LowercaseKeyMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.newrelic.labs; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
@SuppressWarnings("serial") | ||
public class LowercaseKeyMap extends HashMap<String, Object> { | ||
@Override | ||
public Object put(String key, Object value) { | ||
return super.put(key.toLowerCase(), value); | ||
} | ||
|
||
@Override | ||
public void putAll(Map<? extends String, ? extends Object> m) { | ||
for (Map.Entry<? extends String, ? extends Object> entry : m.entrySet()) { | ||
this.put(entry.getKey().toLowerCase(), entry.getValue()); | ||
} | ||
} | ||
} |
Oops, something went wrong.