Skip to content

Commit eaabf14

Browse files
Merge pull request #215 from stokpop/issue-214-add-default-error-callback
added StandardErrorCallback class plus errorCallback field for config
2 parents f4c5d7d + 256fc35 commit eaabf14

18 files changed

+503
-172
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ If you haven't already installed Splunk, download it
2626
[here](http://www.splunk.com/download). For more about installing and running
2727
Splunk and system requirements, see [Installing & Running Splunk](http://dev.splunk.com/view/SP-CAAADRV). Splunk logging for Java is tested with Splunk Enterprise 8.0 and 8.2.0.
2828

29-
#### Java
29+
#### Java
3030

3131
You'll need Java version 8 or higher, from [OpenJDK](https://openjdk.java.net) or [Oracle](https://www.oracle.com/technetwork/java).
3232

@@ -78,4 +78,4 @@ You can [contact support][contact] if you have Splunk related questions.
7878

7979
You can reach the Dev Platform team at [devinfo@splunk.com](mailto:devinfo@splunk.com).
8080

81-
[contact]: https://www.splunk.com/en_us/support-and-services.html
81+
[contact]: https://www.splunk.com/en_us/support-and-services.html

pom.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
<groupId>com.splunk.logging</groupId>
77
<artifactId>splunk-library-javalogging</artifactId>
8-
<version>1.11.4</version>
8+
9+
<version>1.12.0-SNAPSHOT</version>
10+
911
<packaging>jar</packaging>
1012

1113
<name>Splunk Logging for Java</name>

src/main/java/com/splunk/logging/HttpEventCollectorErrorHandler.java

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@
3636
*/
3737
public class HttpEventCollectorErrorHandler {
3838

39+
/**
40+
* Register error handler via full class name.
41+
*
42+
* When the class name is null or empty, null is registered to the <code>HttpEventCollectorErrorHandler</code>.
43+
*
44+
* @param errorCallbackClass the name of the class, for instance: <code>com.splunk.logging.util.StandardErrorCallback</code>
45+
*/
46+
public static void registerClassName(String errorCallbackClass) {
47+
if (errorCallbackClass == null || errorCallbackClass.trim().isEmpty()) {
48+
HttpEventCollectorErrorHandler.onError(null);
49+
return;
50+
}
51+
try {
52+
ErrorCallback callback = (ErrorCallback) Class.forName(errorCallbackClass).newInstance();
53+
HttpEventCollectorErrorHandler.onError(callback);
54+
} catch (final Exception e) {
55+
System.err.println("Warning: cannot create ErrorCallback instance: " + e);
56+
}
57+
}
58+
3959
/**
4060
* This exception is passed to error callback when Splunk server replies an error
4161
*/
@@ -100,10 +120,26 @@ public interface ErrorCallback {
100120
private static ErrorCallback errorCallback;
101121

102122
/**
103-
* Register error callbacks
104-
* @param callback ErrorCallback
123+
* Register error callbacks.
124+
*
125+
* @param callback ErrorCallback Only one ErrorCallback can be registered. A new one will replace the old one.
105126
*/
106127
public static void onError(ErrorCallback callback) {
128+
if (callback == null) {
129+
logInfo("Reset ErrorCallback to null (no error handling).");
130+
}
131+
else {
132+
logInfo("Register ErrorCallback implementation: " + callback);
133+
// onError() is called multiple times in unit tests and is also replaced intentionally.
134+
// Issue a warning when it is replaced by a different kind of handler.
135+
if (errorCallback != null && !errorCallback.equals(callback)) {
136+
logWarn("ErrorCallback instance of '"
137+
+ errorCallback.getClass().getName()
138+
+ "' will be replaced by handler instance of '"
139+
+ callback.getClass().getName()
140+
+ "'");
141+
}
142+
}
107143
errorCallback = callback;
108144
}
109145

@@ -117,4 +153,11 @@ public static void error(final List<HttpEventCollectorEventInfo> data, final Exc
117153
errorCallback.error(data, ex);
118154
}
119155
}
156+
157+
private static void logInfo(String message) {
158+
System.out.println("Info: " + message);
159+
}
160+
private static void logWarn(String message) {
161+
System.out.println("Warning: " + message);
162+
}
120163
}

src/main/java/com/splunk/logging/HttpEventCollectorLog4jAppender.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ public static HttpEventCollectorLog4jAppender createAppender(
148148
@PluginAttribute("disableCertificateValidation") final String disableCertificateValidation,
149149
@PluginAttribute("eventBodySerializer") final String eventBodySerializer,
150150
@PluginAttribute("eventHeaderSerializer") final String eventHeaderSerializer,
151+
@PluginAttribute("errorCallback") final String errorCallback,
151152
@PluginAttribute(value = "includeLoggerName", defaultBoolean = true) final boolean includeLoggerName,
152153
@PluginAttribute(value = "includeThreadName", defaultBoolean = true) final boolean includeThreadName,
153154
@PluginAttribute(value = "includeMDC", defaultBoolean = true) final boolean includeMDC,
@@ -203,6 +204,10 @@ public static HttpEventCollectorLog4jAppender createAppender(
203204
.build();
204205
}
205206

207+
if (errorCallback != null) {
208+
HttpEventCollectorErrorHandler.registerClassName(errorCallback);
209+
}
210+
206211
final boolean ignoreExceptionsBool = Boolean.getBoolean(ignoreExceptions);
207212

208213
return new HttpEventCollectorLog4jAppender(

src/main/java/com/splunk/logging/HttpEventCollectorLogbackAppender.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class HttpEventCollectorLogbackAppender<E> extends AppenderBase<E> {
4747
private String _middleware;
4848
private String _eventBodySerializer;
4949
private String _eventHeaderSerializer;
50+
private String _errorCallback;
5051
private long _batchInterval = 0;
5152
private long _batchCount = 0;
5253
private long _batchSize = 0;
@@ -107,6 +108,10 @@ public void start() {
107108
} catch (final Exception ignored) {}
108109
}
109110

111+
if (_errorCallback != null && !_errorCallback.isEmpty()) {
112+
HttpEventCollectorErrorHandler.registerClassName(_errorCallback);
113+
}
114+
110115
// plug resend middleware
111116
if (_retriesOnError > 0) {
112117
this.sender.addMiddleware(new HttpEventCollectorResendMiddleware(_retriesOnError));
@@ -311,6 +316,10 @@ public String getEventHeaderSerializer() {
311316
return _eventHeaderSerializer;
312317
}
313318

319+
public String getErrorHandler(String errorHandlerClass) {
320+
return this._errorCallback;
321+
}
322+
314323
public void setDisableCertificateValidation(String disableCertificateValidation) {
315324
this._disableCertificateValidation = disableCertificateValidation;
316325
}
@@ -357,6 +366,14 @@ public void setEventHeaderSerializer(String eventHeaderSerializer) {
357366
this._eventHeaderSerializer = eventHeaderSerializer;
358367
}
359368

369+
public void setErrorCallback(String errorHandlerClass) {
370+
this._errorCallback = errorHandlerClass;
371+
}
372+
373+
public String getErrorCallback() {
374+
return this._errorCallback;
375+
}
376+
360377
public void setConnectTimeout(long milliseconds) {
361378
this.timeoutSettings.connectTimeout = milliseconds;
362379
}

src/main/java/com/splunk/logging/HttpEventCollectorLoggingHandler.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ public HttpEventCollectorLoggingHandler() {
157157
String eventHeaderSerializer = getConfigurationProperty("eventHeaderSerializer", "");
158158
String middleware = getConfigurationProperty(MiddlewareTag, null);
159159
String eventBodySerializer = getConfigurationProperty("eventBodySerializer", null);
160+
String errorCallbackClass = getConfigurationProperty("errorCallback", null);
160161

161162
includeLoggerName = getConfigurationBooleanProperty(IncludeLoggerNameConfTag, true);
162163
includeThreadName = getConfigurationBooleanProperty(IncludeThreadNameConfTag, true);
@@ -208,6 +209,16 @@ public HttpEventCollectorLoggingHandler() {
208209
}
209210
}
210211

212+
if (errorCallbackClass != null && !errorCallbackClass.isEmpty()) {
213+
try {
214+
HttpEventCollectorErrorHandler.registerClassName(errorCallbackClass);
215+
} catch (final Exception ex) {
216+
//output error msg but not fail, it will default to use the default EventHeaderSerializer
217+
System.out.println(ex);
218+
}
219+
}
220+
221+
211222
// plug retries middleware
212223
if (retriesOnError > 0) {
213224
this.sender.addMiddleware(new HttpEventCollectorResendMiddleware(retriesOnError));

src/main/java/com/splunk/logging/HttpEventCollectorSender.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -401,10 +401,8 @@ public void completed(int statusCode, String reply) {
401401
}
402402

403403
@Override
404-
public void failed(Exception ex) {
405-
HttpEventCollectorErrorHandler.error(
406-
events,
407-
new HttpEventCollectorErrorHandler.ServerErrorException(ex.getMessage()));
404+
public void failed(Exception exception) {
405+
HttpEventCollectorErrorHandler.error(events, exception);
408406
}
409407
});
410408
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.splunk.logging.util;
2+
3+
import com.splunk.logging.HttpEventCollectorErrorHandler;
4+
import com.splunk.logging.HttpEventCollectorEventInfo;
5+
6+
import java.time.LocalDateTime;
7+
import java.time.format.DateTimeFormatter;
8+
import java.time.format.DateTimeFormatterBuilder;
9+
import java.util.List;
10+
import java.util.Locale;
11+
import java.util.concurrent.atomic.AtomicInteger;
12+
13+
/**
14+
* Print errors to standard out because sending events to Splunk has exceptions and
15+
* logging frameworks might be disabled or broken.
16+
*
17+
* Enable printStackTraces via property: <code>-Dcom.splunk.logging.util.StandardErrorCallback.enablePrintStackTrace=true</code>
18+
*/
19+
public class StandardErrorCallback implements HttpEventCollectorErrorHandler.ErrorCallback {
20+
21+
public static final Locale DEFAULT_LOCALE = Locale.US;
22+
23+
private static final AtomicInteger eventCount = new AtomicInteger(0);
24+
private static final AtomicInteger errorCount = new AtomicInteger(0);
25+
26+
private static final DateTimeFormatter HUMAN_READABLE_WITH_MILLIS =
27+
new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd HH:mm:ss.SSS").toFormatter(DEFAULT_LOCALE);
28+
29+
private final boolean enablePrintStackTrace;
30+
31+
public StandardErrorCallback() {
32+
this(checkEnablePrintStackTrace());
33+
}
34+
35+
private static boolean checkEnablePrintStackTrace() {
36+
return "true".equalsIgnoreCase(System.getProperty("com.splunk.logging.util.StandardErrorCallback.enablePrintStackTrace", "false"));
37+
}
38+
39+
public StandardErrorCallback(boolean enablePrintStackTrace) {
40+
this.enablePrintStackTrace = enablePrintStackTrace;
41+
}
42+
43+
@Override
44+
public void error(List<HttpEventCollectorEventInfo> data, Exception ex) {
45+
int totalErrorCount = errorCount.incrementAndGet();
46+
int totalEventCount = eventCount.addAndGet(data == null ? 0 : data.size());
47+
48+
String threadName = Thread.currentThread().getName();
49+
50+
String fullMessage = createErrorMessage(data, ex, totalErrorCount, totalEventCount, threadName);
51+
52+
printError(fullMessage);
53+
54+
printStackTrace(ex);
55+
}
56+
57+
private String createErrorMessage(List<HttpEventCollectorEventInfo> data, Exception ex, int totalErrorCount, int totalEventCount, String threadName) {
58+
59+
String timestamp = HUMAN_READABLE_WITH_MILLIS.format(LocalDateTime.now());
60+
String exceptionMessage = ex == null ? "unknown (exception null)" : ex.getClass().getSimpleName() + ": " + ex.getMessage();
61+
62+
final String batchOrSingleText = createBatchOrSingleText(data);
63+
64+
String fullMessage = timestamp
65+
+ " [" + threadName + "] HttpEventCollectorError exception for "
66+
+ batchOrSingleText + ". Total errors/events: " + totalErrorCount + "/" + totalEventCount + ". Message: " + exceptionMessage;
67+
68+
return fullMessage;
69+
}
70+
71+
private String createBatchOrSingleText(List<HttpEventCollectorEventInfo> data) {
72+
final String batchOrSingleText;
73+
if (data == null) {
74+
batchOrSingleText = "unknown events (data is null)";
75+
}
76+
else {
77+
int size = data.size();
78+
if (size == 1) {
79+
batchOrSingleText = "log event";
80+
} else {
81+
batchOrSingleText = "batch of " + size + " log events";
82+
}
83+
}
84+
return batchOrSingleText;
85+
}
86+
87+
private void printStackTrace(Exception ex) {
88+
if (enablePrintStackTrace) {
89+
if (ex != null) {
90+
ex.printStackTrace();
91+
}
92+
}
93+
}
94+
95+
private void printError(String fullMessage) {
96+
System.err.println(fullMessage);
97+
}
98+
99+
public int getEventCount() {
100+
return eventCount.get();
101+
}
102+
103+
public int getErrorCount() {
104+
return errorCount.get();
105+
}
106+
107+
public void resetCounters() {
108+
// should maybe be atomic thread safe action, complicated, good enough?
109+
errorCount.set(0);
110+
eventCount.set(0);
111+
}
112+
113+
public boolean isPrintStackTraceEnabled() {
114+
return enablePrintStackTrace;
115+
}
116+
117+
}

0 commit comments

Comments
 (0)