The Splunk Java SDK enables seamless integration between Java applications and Splunk for log ingestion, search, alerting, and dashboard creation. This guide covers comprehensive integration patterns.
Core Concepts
What is Splunk Java SDK?
- Official Java library for interacting with Splunk REST API
- Supports data ingestion, searching, alerting, and dashboard management
- Provides both synchronous and asynchronous operations
Key Use Cases:
- Direct logging from applications to Splunk
- Real-time monitoring and alerting
- Custom dashboards and reporting
- Data export and analysis from Java applications
- Splunk app development
Dependencies and Setup
Maven Dependencies
<properties>
<splunk.version>1.6.5.0</splunk.version>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencies>
<!-- Splunk Java SDK -->
<dependency>
<groupId>com.splunk</groupId>
<artifactId>splunk</artifactId>
<version>${splunk.version}</version>
</dependency>
<!-- Optional: for HTTP communication -->
<dependency>
<groupId>com.splunk</groupId>
<artifactId>splunk-httplib</artifactId>
<version>${splunk.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
Gradle Dependencies
dependencies {
implementation 'com.splunk:splunk:1.6.5.0'
implementation 'com.splunk:splunk-httplib:1.6.5.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
implementation 'org.slf4j:slf4j-api:2.0.7'
}
Core Implementation
1. Splunk Connection Configuration
import com.splunk.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class SplunkConfig {
@Value("${splunk.host:localhost}")
private String host;
@Value("${splunk.port:8089}")
private int port;
@Value("${splunk.username:admin}")
private String username;
@Value("${splunk.password:changeme}")
private String password;
@Value("${splunk.scheme:https}")
private String scheme;
public Service connect() {
try {
// Create connection arguments
ServiceArgs loginArgs = new ServiceArgs();
loginArgs.setHost(host);
loginArgs.setPort(port);
loginArgs.setUsername(username);
loginArgs.setPassword(password);
loginArgs.setScheme(scheme);
// Create service instance
Service service = Service.connect(loginArgs);
// Verify connection
service.getApplications();
return service;
} catch (Exception e) {
throw new SplunkConnectionException("Failed to connect to Splunk", e);
}
}
public Service connectWithToken(String token) {
ServiceArgs loginArgs = new ServiceArgs();
loginArgs.setHost(host);
loginArgs.setPort(port);
loginArgs.setToken(token);
loginArgs.setScheme(scheme);
return Service.connect(loginArgs);
}
}
public class SplunkConnectionException extends RuntimeException {
public SplunkConnectionException(String message, Throwable cause) {
super(message, cause);
}
}
2. Splunk Service Manager
@Component
public class SplunkServiceManager {
private final SplunkConfig splunkConfig;
private Service service;
private static final Logger logger = LoggerFactory.getLogger(SplunkServiceManager.class);
public SplunkServiceManager(SplunkConfig splunkConfig) {
this.splunkConfig = splunkConfig;
}
public synchronized Service getService() {
if (service == null) {
service = splunkConfig.connect();
logger.info("Connected to Splunk instance: {}", service.getHost());
}
return service;
}
public void reconnect() {
if (service != null) {
try {
service.logout();
} catch (Exception e) {
logger.warn("Error during Splunk logout", e);
}
}
service = splunkConfig.connect();
logger.info("Reconnected to Splunk");
}
public boolean isConnected() {
try {
if (service != null) {
service.getApplications();
return true;
}
} catch (Exception e) {
logger.warn("Splunk connection check failed", e);
}
return false;
}
}
Data Ingestion
1. Basic Event Sender
@Component
public class SplunkEventSender {
private final SplunkServiceManager serviceManager;
private final ObjectMapper objectMapper;
private static final Logger logger = LoggerFactory.getLogger(SplunkEventSender.class);
public SplunkEventSender(SplunkServiceManager serviceManager) {
this.serviceManager = serviceManager;
this.objectMapper = new ObjectMapper();
}
public void sendEvent(String index, String source, String sourcetype, Map<String, Object> eventData) {
try {
Service service = serviceManager.getService();
Args receiverArgs = new Args();
receiverArgs.put("sourcetype", sourcetype);
receiverArgs.put("source", source);
// Convert event data to JSON
String eventJson = objectMapper.writeValueAsString(eventData);
// Send event
service.getReceiver().submit(index, receiverArgs, eventJson);
logger.debug("Event sent to Splunk index: {}, source: {}", index, source);
} catch (Exception e) {
logger.error("Failed to send event to Splunk", e);
throw new SplunkIngestionException("Event ingestion failed", e);
}
}
public void sendEvent(String index, String source, String sourcetype, String rawEvent) {
try {
Service service = serviceManager.getService();
Args receiverArgs = new Args();
receiverArgs.put("sourcetype", sourcetype);
receiverArgs.put("source", source);
service.getReceiver().submit(index, receiverArgs, rawEvent);
logger.debug("Raw event sent to Splunk index: {}", index);
} catch (Exception e) {
logger.error("Failed to send raw event to Splunk", e);
throw new SplunkIngestionException("Raw event ingestion failed", e);
}
}
public void sendBatchEvents(String index, String source, String sourcetype,
List<Map<String, Object>> events) {
if (events == null || events.isEmpty()) {
return;
}
try {
Service service = serviceManager.getService();
Args receiverArgs = new Args();
receiverArgs.put("sourcetype", sourcetype);
receiverArgs.put("source", source);
// Convert events to JSON strings
List<String> eventJsons = events.stream()
.map(event -> {
try {
return objectMapper.writeValueAsString(event);
} catch (JsonProcessingException e) {
logger.warn("Failed to serialize event to JSON", e);
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
// Send batch
for (String eventJson : eventJsons) {
service.getReceiver().submit(index, receiverArgs, eventJson);
}
logger.info("Sent {} events to Splunk index: {}", eventJsons.size(), index);
} catch (Exception e) {
logger.error("Failed to send batch events to Splunk", e);
throw new SplunkIngestionException("Batch event ingestion failed", e);
}
}
}
public class SplunkIngestionException extends RuntimeException {
public SplunkIngestionException(String message, Throwable cause) {
super(message, cause);
}
}
2. Structured Event Builder
@Component
public class SplunkEventBuilder {
private final ObjectMapper objectMapper;
public SplunkEventBuilder() {
this.objectMapper = new ObjectMapper();
this.objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"));
}
public Map<String, Object> buildApplicationLog(String level, String message, String loggerName,
Map<String, Object> context) {
Map<String, Object> event = new HashMap<>();
event.put("timestamp", Instant.now().toString());
event.put("level", level);
event.put("message", message);
event.put("logger", loggerName);
event.put("service", "user-service");
event.put("environment", System.getProperty("app.env", "development"));
if (context != null) {
event.putAll(context);
}
return event;
}
public Map<String, Object> buildBusinessEvent(String eventType, String userId,
Map<String, Object> businessData) {
Map<String, Object> event = new HashMap<>();
event.put("timestamp", Instant.now().toString());
event.put("event_type", eventType);
event.put("user_id", userId);
event.put("service", "user-service");
if (businessData != null) {
event.putAll(businessData);
}
return event;
}
public Map<String, Object> buildHttpRequestEvent(String method, String path, int status,
long duration, String userId, String traceId) {
Map<String, Object> event = new HashMap<>();
event.put("timestamp", Instant.now().toString());
event.put("type", "http_request");
event.put("http_method", method);
event.put("http_path", path);
event.put("http_status", status);
event.put("duration_ms", duration);
event.put("user_id", userId);
event.put("trace_id", traceId);
event.put("service", "user-service");
return event;
}
public Map<String, Object> buildErrorEvent(String errorType, String errorMessage,
String stackTrace, Map<String, Object> context) {
Map<String, Object> event = new HashMap<>();
event.put("timestamp", Instant.now().toString());
event.put("type", "error");
event.put("error_type", errorType);
event.put("error_message", errorMessage);
event.put("stack_trace", stackTrace);
event.put("service", "user-service");
if (context != null) {
event.putAll(context);
}
return event;
}
}
3. Async Event Sender with Queue
@Component
public class AsyncSplunkSender {
private final SplunkEventSender eventSender;
private final BlockingQueue<SplunkEvent> eventQueue;
private final ExecutorService executorService;
private volatile boolean running = true;
private static final Logger logger = LoggerFactory.getLogger(AsyncSplunkSender.class);
public AsyncSplunkSender(SplunkEventSender eventSender) {
this.eventSender = eventSender;
this.eventQueue = new LinkedBlockingQueue<>(10000); // Capacity 10,000 events
this.executorService = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "splunk-sender");
t.setDaemon(true);
return t;
});
startConsumer();
}
public void sendEventAsync(String index, String source, String sourcetype,
Map<String, Object> eventData) {
SplunkEvent event = new SplunkEvent(index, source, sourcetype, eventData);
if (!eventQueue.offer(event)) {
logger.warn("Splunk event queue is full, dropping event");
// You could implement a fallback strategy here
}
}
private void startConsumer() {
executorService.submit(() -> {
while (running || !eventQueue.isEmpty()) {
try {
SplunkEvent event = eventQueue.poll(1, TimeUnit.SECONDS);
if (event != null) {
processEvent(event);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
logger.error("Error processing Splunk event from queue", e);
}
}
});
}
private void processEvent(SplunkEvent event) {
try {
eventSender.sendEvent(event.getIndex(), event.getSource(),
event.getSourcetype(), event.getData());
} catch (Exception e) {
logger.error("Failed to send async event to Splunk", e);
// Implement retry logic or dead letter queue
}
}
public void shutdown() {
running = false;
executorService.shutdown();
try {
if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
public int getQueueSize() {
return eventQueue.size();
}
public static class SplunkEvent {
private final String index;
private final String source;
private final String sourcetype;
private final Map<String, Object> data;
public SplunkEvent(String index, String source, String sourcetype, Map<String, Object> data) {
this.index = index;
this.source = source;
this.sourcetype = sourcetype;
this.data = data;
}
// Getters
public String getIndex() { return index; }
public String getSource() { return source; }
public String getSourcetype() { return sourcetype; }
public Map<String, Object> getData() { return data; }
}
}
Data Search and Querying
1. Splunk Search Service
@Component
public class SplunkSearchService {
private final SplunkServiceManager serviceManager;
private final ObjectMapper objectMapper;
private static final Logger logger = LoggerFactory.getLogger(SplunkSearchService.class);
public SplunkSearchService(SplunkServiceManager serviceManager) {
this.serviceManager = serviceManager;
this.objectMapper = new ObjectMapper();
}
public List<Map<String, Object>> search(String searchQuery, String earliestTime,
String latestTime, int maxResults) {
try {
Service service = serviceManager.getService();
// Create search arguments
Args searchArgs = new Args();
searchArgs.put("exec_mode", "blocking");
searchArgs.put("earliest_time", earliestTime);
searchArgs.put("latest_time", latestTime);
searchArgs.put("max_count", maxResults);
// Execute search
Job job = service.getJobs().create(searchQuery, searchArgs);
// Wait for completion
while (!job.isDone()) {
Thread.sleep(100);
}
// Get results
JobResultsArgs resultsArgs = new JobResultsArgs();
resultsArgs.setOutputMode(JobResultsArgs.OutputMode.JSON);
InputStream results = job.getResults(resultsArgs);
return parseSearchResults(results);
} catch (Exception e) {
logger.error("Failed to execute Splunk search: {}", searchQuery, e);
throw new SplunkSearchException("Search execution failed", e);
}
}
public Job createAsyncSearch(String searchQuery, String earliestTime, String latestTime) {
try {
Service service = serviceManager.getService();
Args searchArgs = new Args();
searchArgs.put("exec_mode", "normal");
searchArgs.put("earliest_time", earliestTime);
searchArgs.put("latest_time", latestTime);
return service.getJobs().create(searchQuery, searchArgs);
} catch (Exception e) {
logger.error("Failed to create async Splunk search", e);
throw new SplunkSearchException("Async search creation failed", e);
}
}
public List<Map<String, Object>> getSearchResults(String jobId) {
try {
Service service = serviceManager.getService();
Job job = service.getJob(jobId);
if (!job.isDone()) {
throw new SplunkSearchException("Search job is still running: " + jobId);
}
JobResultsArgs resultsArgs = new JobResultsArgs();
resultsArgs.setOutputMode(JobResultsArgs.OutputMode.JSON);
InputStream results = job.getResults(resultsArgs);
return parseSearchResults(results);
} catch (Exception e) {
logger.error("Failed to get results for job: {}", jobId, e);
throw new SplunkSearchException("Failed to get search results", e);
}
}
public SearchResultsPreview getSearchPreview(String jobId) {
try {
Service service = serviceManager.getService();
Job job = service.getJob(jobId);
JobResultsPreviewArgs previewArgs = new JobResultsPreviewArgs();
previewArgs.setOutputMode(JobResultsPreviewArgs.OutputMode.JSON);
InputStream preview = job.getPreview(previewArgs);
return parseSearchPreview(preview);
} catch (Exception e) {
logger.error("Failed to get preview for job: {}", jobId, e);
throw new SplunkSearchException("Failed to get search preview", e);
}
}
private List<Map<String, Object>> parseSearchResults(InputStream resultsStream) throws IOException {
try (resultsStream) {
JsonNode root = objectMapper.readTree(resultsStream);
JsonNode results = root.path("results");
List<Map<String, Object>> parsedResults = new ArrayList<>();
for (JsonNode result : results) {
Map<String, Object> resultMap = objectMapper.convertValue(result, Map.class);
parsedResults.add(resultMap);
}
return parsedResults;
}
}
private SearchResultsPreview parseSearchPreview(InputStream previewStream) throws IOException {
try (previewStream) {
JsonNode root = objectMapper.readTree(previewStream);
return objectMapper.convertValue(root, SearchResultsPreview.class);
}
}
public static class SearchResultsPreview {
private String sid;
private boolean isDone;
private int resultCount;
private List<Map<String, Object>> results;
// Getters and setters
public String getSid() { return sid; }
public void setSid(String sid) { this.sid = sid; }
public boolean isDone() { return isDone; }
public void setDone(boolean done) { isDone = done; }
public int getResultCount() { return resultCount; }
public void setResultCount(int resultCount) { this.resultCount = resultCount; }
public List<Map<String, Object>> getResults() { return results; }
public void setResults(List<Map<String, Object>> results) { this.results = results; }
}
}
public class SplunkSearchException extends RuntimeException {
public SplunkSearchException(String message, Throwable cause) {
super(message, cause);
}
}
2. Search Query Builder
@Component
public class SplunkQueryBuilder {
public String buildApplicationLogsQuery(String serviceName, String level,
String timeRange, int maxResults) {
return String.format("index=main sourcetype=application_logs service=\"%s\" level=\"%s\" | head %d",
serviceName, level, maxResults);
}
public String buildErrorRateQuery(String serviceName, String timeRange) {
return String.format("index=main sourcetype=application_logs service=\"%s\" " +
"| timechart count(eval(level=\"ERROR\")) as error_count " +
"count as total_count | eval error_rate=round((error_count/total_count)*100, 2)",
serviceName);
}
public String buildHttpPerformanceQuery(String serviceName, String timeRange) {
return String.format("index=main sourcetype=http_logs service=\"%s\" " +
"| stats avg(duration_ms) as avg_duration, max(duration_ms) as max_duration, " +
"p95(duration_ms) as p95_duration by http_path, http_method",
serviceName);
}
public String buildUserActivityQuery(String userId, String timeRange) {
return String.format("index=main sourcetype=user_activity user_id=\"%s\" " +
"| timechart count by event_type",
userId);
}
public String buildBusinessMetricsQuery(String metricName, String timeRange) {
return String.format("index=main sourcetype=business_metrics metric_name=\"%s\" " +
"| timechart avg(metric_value) as avg_value",
metricName);
}
}
Alerting and Monitoring
1. Alert Manager
@Component
public class SplunkAlertManager {
private final SplunkServiceManager serviceManager;
private static final Logger logger = LoggerFactory.getLogger(SplunkAlertManager.class);
public SplunkAlertManager(SplunkServiceManager serviceManager) {
this.serviceManager = serviceManager;
}
public void createAlert(String alertName, String searchQuery, String cronSchedule,
String action, Map<String, String> parameters) {
try {
Service service = serviceManager.getService();
// Create saved search for alert
Args savedSearchArgs = new Args();
savedSearchArgs.put("search", searchQuery);
savedSearchArgs.put("cron_schedule", cronSchedule);
savedSearchArgs.put("alert_type", "number of events");
savedSearchArgs.put("alert_comparator", "greater than");
savedSearchArgs.put("alert_threshold", "0");
savedSearchArgs.put("alert.digest_mode", "0");
savedSearchArgs.put("dispatch.earliest_time", "-1h");
savedSearchArgs.put("dispatch.latest_time", "now");
// Add actions
if ("email".equals(action)) {
savedSearchArgs.put("action.email", "1");
savedSearchArgs.put("action.email.to", parameters.get("email_to"));
savedSearchArgs.put("action.email.subject", parameters.get("subject"));
savedSearchArgs.put("action.email.message", parameters.get("message"));
} else if ("webhook".equals(action)) {
savedSearchArgs.put("action.webhook", "1");
savedSearchArgs.put("action.webhook.url", parameters.get("webhook_url"));
}
SavedSearch savedSearch = service.getSavedSearches().create(alertName, searchQuery, savedSearchArgs);
savedSearch.enable();
logger.info("Created Splunk alert: {}", alertName);
} catch (Exception e) {
logger.error("Failed to create Splunk alert: {}", alertName, e);
throw new SplunkAlertException("Alert creation failed", e);
}
}
public List<SavedSearch> getAlerts() {
try {
Service service = serviceManager.getService();
return new ArrayList<>(service.getSavedSearches().values());
} catch (Exception e) {
logger.error("Failed to retrieve Splunk alerts", e);
throw new SplunkAlertException("Failed to get alerts", e);
}
}
public void disableAlert(String alertName) {
try {
Service service = serviceManager.getService();
SavedSearch alert = service.getSavedSearches().get(alertName);
if (alert != null) {
alert.disable();
logger.info("Disabled Splunk alert: {}", alertName);
}
} catch (Exception e) {
logger.error("Failed to disable Splunk alert: {}", alertName, e);
throw new SplunkAlertException("Alert disable failed", e);
}
}
public void deleteAlert(String alertName) {
try {
Service service = serviceManager.getService();
service.getSavedSearches().remove(alertName);
logger.info("Deleted Splunk alert: {}", alertName);
} catch (Exception e) {
logger.error("Failed to delete Splunk alert: {}", alertName, e);
throw new SplunkAlertException("Alert deletion failed", e);
}
}
}
public class SplunkAlertException extends RuntimeException {
public SplunkAlertException(String message, Throwable cause) {
super(message, cause);
}
}
Spring Boot Integration
1. Configuration Properties
# application.yml
splunk:
host: ${SPLUNK_HOST:localhost}
port: ${SPLUNK_PORT:8089}
username: ${SPLUNK_USERNAME:admin}
password: ${SPLUNK_PASSWORD:changeme}
scheme: ${SPLUNK_SCHEME:https}
# Index configuration
index:
application: main
metrics: metrics
alerts: alerts
# Sourcetype configuration
sourcetype:
application: application_logs
http: http_logs
business: business_events
# Async sender configuration
async:
enabled: true
queue-size: 10000
batch-size: 100
2. Configuration Class
@Configuration
@EnableConfigurationProperties(SplunkProperties.class)
public class SplunkAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SplunkConfig splunkConfig(SplunkProperties properties) {
return new SplunkConfig();
}
@Bean
@ConditionalOnMissingBean
public SplunkServiceManager splunkServiceManager(SplunkConfig splunkConfig) {
return new SplunkServiceManager(splunkConfig);
}
@Bean
@ConditionalOnMissingBean
public SplunkEventSender splunkEventSender(SplunkServiceManager serviceManager) {
return new SplunkEventSender(serviceManager);
}
@Bean
@ConditionalOnMissingBean
public SplunkEventBuilder splunkEventBuilder() {
return new SplunkEventBuilder();
}
@Bean
@ConditionalOnProperty(name = "splunk.async.enabled", havingValue = "true")
public AsyncSplunkSender asyncSplunkSender(SplunkEventSender eventSender) {
return new AsyncSplunkSender(eventSender);
}
@Bean
@ConditionalOnMissingBean
public SplunkSearchService splunkSearchService(SplunkServiceManager serviceManager) {
return new SplunkSearchService(serviceManager);
}
@Bean
@ConditionalOnMissingBean
public SplunkAlertManager splunkAlertManager(SplunkServiceManager serviceManager) {
return new SplunkAlertManager(serviceManager);
}
}
@ConfigurationProperties(prefix = "splunk")
public class SplunkProperties {
private String host;
private int port;
private String username;
private String password;
private String scheme;
private Index index = new Index();
private Sourcetype sourcetype = new Sourcetype();
private Async async = new Async();
// Getters and setters
public static class Index {
private String application;
private String metrics;
private String alerts;
// getters and setters
}
public static class Sourcetype {
private String application;
private String http;
private String business;
// getters and setters
}
public static class Async {
private boolean enabled;
private int queueSize;
private int batchSize;
// getters and setters
}
}
3. Service Usage Examples
@Service
public class UserService {
private final AsyncSplunkSender splunkSender;
private final SplunkEventBuilder eventBuilder;
private final SplunkProperties splunkProperties;
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public UserService(AsyncSplunkSender splunkSender, SplunkEventBuilder eventBuilder,
SplunkProperties splunkProperties) {
this.splunkSender = splunkSender;
this.eventBuilder = eventBuilder;
this.splunkProperties = splunkProperties;
}
public User createUser(CreateUserRequest request) {
Map<String, Object> context = Map.of(
"userId", "pending",
"email", request.getEmail(),
"username", request.getUsername(),
"operation", "create_user"
);
Map<String, Object> startEvent = eventBuilder.buildApplicationLog(
"INFO", "Starting user creation", "UserService", context);
splunkSender.sendEventAsync(
splunkProperties.getIndex().getApplication(),
"user-service",
splunkProperties.getSourcetype().getApplication(),
startEvent);
try {
// Business logic
User user = userRepository.save(request.toUser());
// Log success
Map<String, Object> successEvent = eventBuilder.buildApplicationLog(
"INFO", "User created successfully", "UserService",
Map.of("userId", user.getId(), "email", user.getEmail()));
splunkSender.sendEventAsync(
splunkProperties.getIndex().getApplication(),
"user-service",
splunkProperties.getSourcetype().getApplication(),
successEvent);
// Business event
Map<String, Object> businessEvent = eventBuilder.buildBusinessEvent(
"user_created", user.getId(),
Map.of("email", user.getEmail(), "plan", user.getPlan()));
splunkSender.sendEventAsync(
splunkProperties.getIndex().getApplication(),
"user-service",
splunkProperties.getSourcetype().getBusiness(),
businessEvent);
return user;
} catch (Exception e) {
// Log error
Map<String, Object> errorEvent = eventBuilder.buildErrorEvent(
e.getClass().getSimpleName(), e.getMessage(),
getStackTrace(e), context);
splunkSender.sendEventAsync(
splunkProperties.getIndex().getApplication(),
"user-service",
splunkProperties.getSourcetype().getApplication(),
errorEvent);
throw e;
}
}
private String getStackTrace(Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
}
4. REST Controller for Splunk Operations
@RestController
@RequestMapping("/api/splunk")
public class SplunkController {
private final SplunkSearchService searchService;
private final SplunkQueryBuilder queryBuilder;
private final SplunkAlertManager alertManager;
public SplunkController(SplunkSearchService searchService, SplunkQueryBuilder queryBuilder,
SplunkAlertManager alertManager) {
this.searchService = searchService;
this.queryBuilder = queryBuilder;
this.alertManager = alertManager;
}
@GetMapping("/search/application-logs")
public ResponseEntity<List<Map<String, Object>>> searchApplicationLogs(
@RequestParam String serviceName,
@RequestParam(defaultValue = "INFO") String level,
@RequestParam(defaultValue = "-1h") String timeRange,
@RequestParam(defaultValue = "100") int maxResults) {
String query = queryBuilder.buildApplicationLogsQuery(serviceName, level, timeRange, maxResults);
List<Map<String, Object>> results = searchService.search(query, timeRange, "now", maxResults);
return ResponseEntity.ok(results);
}
@GetMapping("/metrics/error-rate")
public ResponseEntity<List<Map<String, Object>>> getErrorRate(
@RequestParam String serviceName,
@RequestParam(defaultValue = "-24h") String timeRange) {
String query = queryBuilder.buildErrorRateQuery(serviceName, timeRange);
List<Map<String, Object>> results = searchService.search(query, timeRange, "now", 1000);
return ResponseEntity.ok(results);
}
@PostMapping("/alerts")
public ResponseEntity<String> createAlert(@RequestBody CreateAlertRequest request) {
alertManager.createAlert(
request.getName(),
request.getSearchQuery(),
request.getSchedule(),
request.getAction(),
request.getParameters());
return ResponseEntity.ok("Alert created successfully");
}
@GetMapping("/alerts")
public ResponseEntity<List<SavedSearch>> getAlerts() {
List<SavedSearch> alerts = alertManager.getAlerts();
return ResponseEntity.ok(alerts);
}
}
Testing
1. Unit Tests with Mocking
@ExtendWith(MockitoExtension.class)
class SplunkEventSenderTest {
@Mock
private SplunkServiceManager serviceManager;
@Mock
private Service splunkService;
@Mock
private Receiver receiver;
private SplunkEventSender eventSender;
@BeforeEach
void setUp() {
when(serviceManager.getService()).thenReturn(splunkService);
when(splunkService.getReceiver()).thenReturn(receiver);
eventSender = new SplunkEventSender(serviceManager);
}
@Test
void shouldSendEventToSplunk() {
// Given
Map<String, Object> eventData = Map.of(
"message", "Test event",
"level", "INFO",
"timestamp", Instant.now().toString()
);
// When
eventSender.sendEvent("main", "test-source", "test-sourcetype", eventData);
// Then
verify(receiver).submit(eq("main"), any(Args.class), anyString());
}
}
@SpringBootTest
@TestPropertySource(properties = {
"splunk.host=localhost",
"splunk.port=8089",
"splunk.async.enabled=true"
})
class SplunkIntegrationTest {
@Autowired
private AsyncSplunkSender asyncSender;
@Test
void shouldSendEventAsync() {
// Given
Map<String, Object> eventData = Map.of(
"message", "Integration test event",
"level", "INFO"
);
// When
asyncSender.sendEventAsync("main", "test", "application", eventData);
// Then - wait for async processing
await().atMost(5, TimeUnit.SECONDS)
.until(() -> asyncSender.getQueueSize() == 0);
}
}
Best Practices
- Connection Management: Implement connection pooling and reconnection logic
- Error Handling: Graceful degradation when Splunk is unavailable
- Performance: Use async sending for high-throughput applications
- Security: Secure Splunk credentials and use tokens when possible
- Monitoring: Monitor queue sizes and error rates
- Data Retention: Configure appropriate index retention policies
// Health check component
@Component
public class SplunkHealthIndicator implements HealthIndicator {
private final SplunkServiceManager serviceManager;
private final AsyncSplunkSender asyncSender;
@Override
public Health health() {
try {
if (!serviceManager.isConnected()) {
return Health.down()
.withDetail("error", "Splunk connection failed")
.build();
}
int queueSize = asyncSender.getQueueSize();
Health.Builder healthBuilder = Health.up()
.withDetail("queueSize", queueSize);
if (queueSize > 5000) {
healthBuilder.withDetail("warning", "Queue size is high");
}
return healthBuilder.build();
} catch (Exception e) {
return Health.down(e).build();
}
}
}
Conclusion
The Splunk Java SDK provides powerful capabilities for:
- Real-time data ingestion from Java applications
- Advanced search and analytics through Java code
- Proactive alerting and monitoring
- Custom dashboard and reporting integration
By implementing the patterns shown above, you can build robust, production-ready integrations that leverage Splunk's full capabilities while maintaining application performance and reliability. The combination of async processing, proper error handling, and Spring Boot integration creates a solid foundation for enterprise-grade observability.