This is an automated email from the ASF dual-hosted git repository.

epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-mcp.git


The following commit(s) were added to refs/heads/main by this push:
     new 1883124  feat: add SLF4J logging to all service classes (#110)
1883124 is described below

commit 1883124446f71984d2ea50fb117044e68f8fb3c8
Author: Aditya Parikh <[email protected]>
AuthorDate: Fri Sep 11 10:29:07 2026 -0400

    feat: add SLF4J logging to all service classes (#110)
    
    * feat: add SLF4J logging to all service classes
    
    Add SLF4J loggers to CollectionService, IndexingService, SchemaService,
    SearchService, and JsonUtils. Log exceptions in all catch blocks instead
    of silently swallowing them. Use appropriate log levels: error for
    operational failures, warn for recoverable issues, debug for expected
    conditions (Solr 10 metrics unavailability, individual doc failures).
    
    Safe for STDIO mode: logback-spring.xml already suppresses console
    logging in the stdio profile.
    
    Closes #1
    
    Signed-off-by: Aditya Parikh <[email protected]>
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
    Signed-off-by: adityamparikh <[email protected]>
    
    * refactor(collection): narrow the metrics catch clauses to SolrException
    
    Adopts the narrowing from #111 so the two PRs compose instead of colliding.
    
    Both PRs rewrite the same two catch clauses in fetchCacheMetrics and
    fetchHandlerMetrics. #111 narrows RuntimeException to SolrException; this PR
    was binding the exception for logging while leaving RuntimeException in 
place.
    Whichever merged second would either conflict or silently revert the other's
    intent — so this branch now carries the narrowed form too, and the end 
state is
    the same in either merge order.
    
    RemoteSolrException extends SolrException (verified against solrj 10.0.0), 
so
    the Solr 10 path where /admin/mbeans is gone still degrades to null rather 
than
    propagating. What no longer gets swallowed is unrelated RuntimeExceptions --
    which is the point of #111, and is what the new debug logging is there to
    surface.
    
    Signed-off-by: Aditya Parikh <[email protected]>
    
    * refactor(search): log query failures before rethrowing with a hint
    
    SearchService gained a logger in the SLF4J commit but never used it:
    #166 landed afterwards and made every SolrException rethrow wrapped in
    an IllegalArgumentException carrying a remediation hint, so nothing is
    swallowed there any more.
    
    The MCP client sees only the exception message, so the server kept no
    record of a failed query at all. Log one debug line in
    withRemediationHint covering every path, including the no-hint
    fallback. Debug rather than warn because the failure is already
    reported to the caller; the stdio profile defines no appenders, so
    this cannot reach stdout.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_015rXANeQAujgxhEz1uAWFm9
    Signed-off-by: Aditya Parikh <[email protected]>
    
    ---------
    
    Signed-off-by: Aditya Parikh <[email protected]>
    Signed-off-by: adityamparikh <[email protected]>
    Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../mcp/server/collection/CollectionService.java   | 22 +++++++++++++++-------
 .../solr/mcp/server/indexing/IndexingService.java  | 15 +++++++++++++--
 .../solr/mcp/server/schema/SchemaService.java      |  5 +++++
 .../solr/mcp/server/search/SearchService.java      |  8 ++++++++
 .../org/apache/solr/mcp/server/util/JsonUtils.java |  5 +++++
 5 files changed, 46 insertions(+), 9 deletions(-)

diff --git 
a/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java 
b/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java
index 011d278..6786082 100644
--- a/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java
+++ b/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java
@@ -40,11 +40,14 @@ import 
org.apache.solr.client.solrj.response.CollectionAdminResponse;
 import org.apache.solr.client.solrj.response.LukeResponse;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.SolrPingResponse;
+import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.mcp.server.config.SolrConfigurationProperties;
 import org.apache.solr.mcp.server.util.PromptNames;
 import org.jspecify.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springaicommunity.mcp.annotation.McpArg;
 import org.springaicommunity.mcp.annotation.McpComplete;
 import org.springaicommunity.mcp.annotation.McpPrompt;
@@ -136,6 +139,8 @@ import org.springframework.stereotype.Service;
 @Observed
 public class CollectionService {
 
+       private static final Logger logger = 
LoggerFactory.getLogger(CollectionService.class);
+
        // ========================================
        // Constants for API Parameters and Paths
        // ========================================
@@ -683,16 +688,17 @@ public class CollectionService {
         * Internal cache metrics fetch that assumes the collection has already 
been
         * validated and the name has been extracted from any shard identifier.
         */
-       private @Nullable CacheStats fetchCacheMetrics(String collection) {
+       private @Nullable CacheStats fetchCacheMetrics(String collectionName) {
                try {
-                       NamedList<Object> coreMetrics = 
fetchMetrics(collection, CACHE_METRIC_PREFIX);
+                       NamedList<Object> coreMetrics = 
fetchMetrics(collectionName, CACHE_METRIC_PREFIX);
                        if (coreMetrics == null) {
                                return null;
                        }
 
                        CacheStats stats = extractCacheStats(coreMetrics);
                        return isCacheStatsEmpty(stats) ? null : stats;
-               } catch (SolrServerException | IOException | RuntimeException 
_) {
+               } catch (SolrServerException | IOException | SolrException e) {
+                       logger.debug("Cache metrics unavailable for collection: 
{}", collectionName, e);
                        return null;
                }
        }
@@ -799,18 +805,19 @@ public class CollectionService {
         * Internal handler metrics fetch that assumes the collection has 
already been
         * validated and the name has been extracted from any shard identifier.
         */
-       private @Nullable HandlerStats fetchHandlerMetrics(String collection) {
+       private @Nullable HandlerStats fetchHandlerMetrics(String 
collectionName) {
                try {
                        // Handler metrics are flat keys (e.g. 
QUERY./select.requests) so we
                        // fetch each handler prefix separately and reconstruct 
HandlerInfo
-                       HandlerInfo selectHandler = 
fetchFlatHandlerInfo(collection, SELECT_HANDLER_METRIC_PREFIX,
+                       HandlerInfo selectHandler = 
fetchFlatHandlerInfo(collectionName, SELECT_HANDLER_METRIC_PREFIX,
                                        SELECT_HANDLER_KEY);
-                       HandlerInfo updateHandler = 
fetchFlatHandlerInfo(collection, UPDATE_HANDLER_METRIC_PREFIX,
+                       HandlerInfo updateHandler = 
fetchFlatHandlerInfo(collectionName, UPDATE_HANDLER_METRIC_PREFIX,
                                        UPDATE_HANDLER_KEY);
 
                        HandlerStats stats = new HandlerStats(selectHandler, 
updateHandler);
                        return isHandlerStatsEmpty(stats) ? null : stats;
-               } catch (SolrServerException | IOException | RuntimeException 
_) {
+               } catch (SolrServerException | IOException | SolrException e) {
+                       logger.debug("Handler metrics unavailable for 
collection: {}", collectionName, e);
                        return null;
                }
        }
@@ -1080,6 +1087,7 @@ public class CollectionService {
                                        
statsResponse.getResults().getNumFound(), Instant.now(), actualCollection);
 
                } catch (Exception e) {
+                       logger.warn("Health check failed for collection: {}", 
collection, e);
                        return new SolrHealthStatus(false, e.getMessage(), 
null, null, Instant.now(), actualCollection);
                }
        }
diff --git 
a/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java 
b/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java
index 5ac3704..1350496 100644
--- a/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java
+++ b/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java
@@ -29,6 +29,8 @@ import org.apache.solr.common.SolrInputDocument;
 import 
org.apache.solr.mcp.server.indexing.documentcreator.IndexingDocumentCreator;
 import org.apache.solr.mcp.server.util.PromptNames;
 import org.apache.solr.mcp.server.util.PromptText;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springaicommunity.mcp.annotation.McpArg;
 import org.springaicommunity.mcp.annotation.McpPrompt;
 import org.springaicommunity.mcp.annotation.McpTool;
@@ -116,6 +118,8 @@ import org.xml.sax.SAXException;
 @Observed
 public class IndexingService {
 
+       private static final Logger logger = 
LoggerFactory.getLogger(IndexingService.class);
+
        private static final int DEFAULT_BATCH_SIZE = 1000;
 
        /** SolrJ client for communicating with Solr server */
@@ -578,12 +582,14 @@ public class IndexingService {
                                solrClient.add(collection, batch);
                                successCount += batch.size();
                        } catch (SolrServerException | IOException | 
RuntimeException e) {
+                               logger.warn("Batch indexing failed, retrying 
individually", e);
                                // Try indexing documents individually to 
identify problematic ones
                                for (SolrInputDocument doc : batch) {
                                        try {
                                                solrClient.add(collection, doc);
                                                successCount++;
-                                       } catch (SolrServerException | 
IOException | RuntimeException _) {
+                                       } catch (SolrServerException | 
IOException | RuntimeException e2) {
+                                               logger.debug("Failed to index 
individual document", e2);
                                                // Document failed to index - 
this is expected behavior for problematic
                                                // documents
                                                // We continue processing the 
rest of the batch
@@ -592,7 +598,12 @@ public class IndexingService {
                        }
                }
 
-               solrClient.commit(collection);
+               try {
+                       solrClient.commit(collection);
+               } catch (SolrServerException | IOException e) {
+                       logger.error("Failed to commit after indexing to 
collection: {}", collection, e);
+                       throw e;
+               }
                return successCount;
        }
 
diff --git a/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java 
b/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java
index 3f3bb96..73bb340 100644
--- a/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java
+++ b/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java
@@ -33,6 +33,8 @@ import 
org.apache.solr.client.solrj.request.schema.FieldTypeDefinition;
 import org.apache.solr.client.solrj.request.schema.SchemaRequest;
 import org.apache.solr.client.solrj.response.schema.SchemaRepresentation;
 import org.apache.solr.mcp.server.util.PromptNames;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springaicommunity.mcp.annotation.McpArg;
 import org.springaicommunity.mcp.annotation.McpPrompt;
 import org.springaicommunity.mcp.annotation.McpResource;
@@ -137,6 +139,8 @@ import org.springframework.stereotype.Service;
 @Observed
 public class SchemaService {
 
+       private static final Logger logger = 
LoggerFactory.getLogger(SchemaService.class);
+
        /** SolrJ client for communicating with Solr server */
        private final SolrClient solrClient;
 
@@ -185,6 +189,7 @@ public class SchemaService {
                try {
                        return toJson(objectMapper, getSchema(collection));
                } catch (Exception e) {
+                       logger.error("Failed to get schema for collection: {}", 
collection, e);
                        // Serialise via Jackson rather than concatenating: an 
exception message
                        // containing a quote, backslash or newline would 
otherwise emit invalid
                        // JSON to the MCP client.
diff --git a/src/main/java/org/apache/solr/mcp/server/search/SearchService.java 
b/src/main/java/org/apache/solr/mcp/server/search/SearchService.java
index 2fb9800..c9dda13 100644
--- a/src/main/java/org/apache/solr/mcp/server/search/SearchService.java
+++ b/src/main/java/org/apache/solr/mcp/server/search/SearchService.java
@@ -34,6 +34,8 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.FacetParams;
 import org.apache.solr.mcp.server.util.PromptNames;
 import org.jspecify.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springaicommunity.mcp.annotation.McpArg;
 import org.springaicommunity.mcp.annotation.McpPrompt;
 import org.springaicommunity.mcp.annotation.McpTool;
@@ -108,6 +110,8 @@ import org.springframework.util.StringUtils;
 @Observed
 public class SearchService {
 
+       private static final Logger logger = 
LoggerFactory.getLogger(SearchService.class);
+
        /**
         * Fragments of Solr's own error text that identify a failure we can 
advise on.
         *
@@ -370,6 +374,10 @@ public class SearchService {
        private static RuntimeException withRemediationHint(SolrException e, 
String collection) {
                final String message = String.valueOf(e.getMessage());
 
+               // The MCP client only ever sees the exception message, so 
without this the
+               // server keeps no record of a failed query.
+               logger.debug("Solr query failed on collection {}", collection, 
e);
+
                // An unknown collection is a 404 whose body is Solr's HTML 
"not found" page,
                // so SolrJ reports it as a mime-type mismatch and leaves 
getMetadata() null.
                // The status code is the only signal that survives; match it 
rather than the
diff --git a/src/main/java/org/apache/solr/mcp/server/util/JsonUtils.java 
b/src/main/java/org/apache/solr/mcp/server/util/JsonUtils.java
index 6ecc3bc..44c36a8 100644
--- a/src/main/java/org/apache/solr/mcp/server/util/JsonUtils.java
+++ b/src/main/java/org/apache/solr/mcp/server/util/JsonUtils.java
@@ -18,6 +18,8 @@ package org.apache.solr.mcp.server.util;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Utility class for JSON serialization operations.
@@ -31,6 +33,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
  */
 public final class JsonUtils {
 
+       private static final Logger logger = 
LoggerFactory.getLogger(JsonUtils.class);
+
        private JsonUtils() {
                // Utility class - prevent instantiation
        }
@@ -52,6 +56,7 @@ public final class JsonUtils {
                try {
                        return objectMapper.writeValueAsString(obj);
                } catch (JsonProcessingException e) {
+                       logger.error("Failed to serialize response", e);
                        return "{\"error\": \"Failed to serialize response\"}";
                }
        }

Reply via email to