exception handling error to warn etc (#3866)

# Description of Changes

Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
This commit is contained in:
Anthony Stirling 2025-07-03 11:36:06 +01:00 committed by GitHub
parent a95743286e
commit 7617e5176b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 128 additions and 60 deletions

View File

@ -411,7 +411,7 @@ public class CustomPDFDocumentFactory {
try { try {
document.setAllSecurityToBeRemoved(true); document.setAllSecurityToBeRemoved(true);
} catch (Exception e) { } catch (Exception e) {
log.error("Decryption failed", e); ExceptionUtils.logException("PDF decryption", e);
throw new IOException("PDF decryption failed", e); throw new IOException("PDF decryption failed", e);
} }
} }

View File

@ -72,12 +72,12 @@ public class TempFileCleanupService {
fileName -> fileName ->
fileName.contains("jetty") fileName.contains("jetty")
|| fileName.startsWith("jetty-") || fileName.startsWith("jetty-")
|| fileName.equals("proc") || "proc".equals(fileName)
|| fileName.equals("sys") || "sys".equals(fileName)
|| fileName.equals("dev") || "dev".equals(fileName)
|| fileName.equals("hsperfdata_stirlingpdfuser") || "hsperfdata_stirlingpdfuser".equals(fileName)
|| fileName.startsWith("hsperfdata_") || fileName.startsWith("hsperfdata_")
|| fileName.equals(".pdfbox.cache"); || ".pdfbox.cache".equals(fileName);
@PostConstruct @PostConstruct
public void init() { public void init() {
@ -154,11 +154,13 @@ public class TempFileCleanupService {
boolean containerMode = isContainerMode(); boolean containerMode = isContainerMode();
int unregisteredDeletedCount = cleanupUnregisteredFiles(containerMode, true, maxAgeMillis); int unregisteredDeletedCount = cleanupUnregisteredFiles(containerMode, true, maxAgeMillis);
if(registeredDeletedCount >0 || unregisteredDeletedCount >0 || directoriesDeletedCount >0) {
log.info( log.info(
"Scheduled cleanup complete. Deleted {} registered files, {} unregistered files, {} directories", "Scheduled cleanup complete. Deleted {} registered files, {} unregistered files, {} directories",
registeredDeletedCount, registeredDeletedCount,
unregisteredDeletedCount, unregisteredDeletedCount,
directoriesDeletedCount); directoriesDeletedCount);
}
} }
/** /**
@ -166,7 +168,6 @@ public class TempFileCleanupService {
* important in Docker environments where temp files persist between container restarts. * important in Docker environments where temp files persist between container restarts.
*/ */
private void runStartupCleanup() { private void runStartupCleanup() {
log.info("Running startup temporary file cleanup");
boolean containerMode = isContainerMode(); boolean containerMode = isContainerMode();
log.info( log.info(
@ -178,7 +179,6 @@ public class TempFileCleanupService {
long maxAgeMillis = containerMode ? 0 : 24 * 60 * 60 * 1000; // 0 or 24 hours long maxAgeMillis = containerMode ? 0 : 24 * 60 * 60 * 1000; // 0 or 24 hours
int totalDeletedCount = cleanupUnregisteredFiles(containerMode, false, maxAgeMillis); int totalDeletedCount = cleanupUnregisteredFiles(containerMode, false, maxAgeMillis);
log.info( log.info(
"Startup cleanup complete. Deleted {} temporary files/directories", "Startup cleanup complete. Deleted {} temporary files/directories",
totalDeletedCount); totalDeletedCount);
@ -225,7 +225,7 @@ public class TempFileCleanupService {
tempDir -> { tempDir -> {
try { try {
String phase = isScheduled ? "scheduled" : "startup"; String phase = isScheduled ? "scheduled" : "startup";
log.info( log.debug(
"Scanning directory for {} cleanup: {}", "Scanning directory for {} cleanup: {}",
phase, phase,
tempDir); tempDir);

View File

@ -298,9 +298,9 @@ public class ExceptionUtils {
* @param e the exception that occurred * @param e the exception that occurred
*/ */
public static void logException(String operation, Exception e) { public static void logException(String operation, Exception e) {
if (e instanceof IOException && PdfErrorUtils.isCorruptedPdfError((IOException) e)) { if (PdfErrorUtils.isCorruptedPdfError(e)) {
log.warn("PDF corruption detected during {}: {}", operation, e.getMessage()); log.warn("PDF corruption detected during {}: {}", operation, e.getMessage());
} else if (isEncryptionError((IOException) e) || isPasswordError((IOException) e)) { } else if (e instanceof IOException && (isEncryptionError((IOException) e) || isPasswordError((IOException) e))) {
log.info("PDF security issue during {}: {}", operation, e.getMessage()); log.info("PDF security issue during {}: {}", operation, e.getMessage());
} else { } else {
log.error("Unexpected error during {}", operation, e); log.error("Unexpected error during {}", operation, e);

View File

@ -12,7 +12,26 @@ public class PdfErrorUtils {
* @return true if the error indicates PDF corruption, false otherwise * @return true if the error indicates PDF corruption, false otherwise
*/ */
public static boolean isCorruptedPdfError(IOException e) { public static boolean isCorruptedPdfError(IOException e) {
String message = e.getMessage(); return isCorruptedPdfError(e.getMessage());
}
/**
* Checks if any Exception indicates a corrupted PDF file.
*
* @param e the Exception to check
* @return true if the error indicates PDF corruption, false otherwise
*/
public static boolean isCorruptedPdfError(Exception e) {
return isCorruptedPdfError(e.getMessage());
}
/**
* Checks if an error message indicates a corrupted PDF file.
*
* @param message the error message to check
* @return true if the message indicates PDF corruption, false otherwise
*/
private static boolean isCorruptedPdfError(String message) {
if (message == null) return false; if (message == null) return false;
// Check for common corruption indicators // Check for common corruption indicators
@ -24,6 +43,10 @@ public class PdfErrorUtils {
|| message.contains("damaged") || message.contains("damaged")
|| message.contains("Unknown dir object") || message.contains("Unknown dir object")
|| message.contains("Can't dereference COSObject") || message.contains("Can't dereference COSObject")
|| message.contains("parseCOSString string should start with")
|| message.contains("ICCBased colorspace array must have a stream")
|| message.contains("1-based index not found")
|| message.contains("Invalid dictionary, found:")
|| message.contains("AES initialization vector not fully read") || message.contains("AES initialization vector not fully read")
|| message.contains("BadPaddingException") || message.contains("BadPaddingException")
|| message.contains("Given final block not properly padded"); || message.contains("Given final block not properly padded");

View File

@ -148,7 +148,7 @@ public class MergeController {
try (PDDocument doc = pdfDocumentFactory.load(file)) { try (PDDocument doc = pdfDocumentFactory.load(file)) {
pageIndex += doc.getNumberOfPages(); pageIndex += doc.getNumberOfPages();
} catch (IOException e) { } catch (IOException e) {
log.error("Error loading document for TOC generation", e); ExceptionUtils.logException("document loading for TOC generation", e);
pageIndex++; // Increment by at least one if we can't determine page count pageIndex++; // Increment by at least one if we can't determine page count
} }
} }
@ -240,7 +240,11 @@ public class MergeController {
baos, mergedFileName); // Return the modified PDF baos, mergedFileName); // Return the modified PDF
} catch (Exception ex) { } catch (Exception ex) {
log.error("Error in merge pdf process", ex); if (ex instanceof IOException && PdfErrorUtils.isCorruptedPdfError((IOException) ex)) {
log.warn("Corrupted PDF detected in merge pdf process: {}", ex.getMessage());
} else {
log.error("Error in merge pdf process", ex);
}
throw ex; throw ex;
} finally { } finally {
if (mergedDocument != null) { if (mergedDocument != null) {

View File

@ -25,6 +25,7 @@ import stirling.software.SPDF.model.SortTypes;
import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.general.RearrangePagesRequest; import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils; import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils; import stirling.software.common.util.WebResponseUtils;
@ -288,7 +289,7 @@ public class RearrangePagesPDFController {
.replaceFirst("[.][^.]+$", "") .replaceFirst("[.][^.]+$", "")
+ "_rearranged.pdf"); + "_rearranged.pdf");
} catch (IOException e) { } catch (IOException e) {
log.error("Failed rearranging documents", e); ExceptionUtils.logException("document rearrangement", e);
throw e; throw e;
} }
} }

View File

@ -29,6 +29,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils; import stirling.software.common.util.WebResponseUtils;
@RestController @RestController
@ -96,7 +97,7 @@ public class SplitPDFController {
splitDocumentsBoas.add(baos); splitDocumentsBoas.add(baos);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed splitting documents and saving them", e); ExceptionUtils.logException("document splitting and saving", e);
throw e; throw e;
} }
} }

View File

@ -159,7 +159,7 @@ public class SplitPdfByChaptersController {
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1); Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
} catch (Exception e) { } catch (Exception e) {
log.error("Unable to extract outline items", e); ExceptionUtils.logException("outline extraction", e);
return ResponseEntity.internalServerError() return ResponseEntity.internalServerError()
.body("Unable to extract outline items".getBytes()); .body("Unable to extract outline items".getBytes());
} }
@ -294,7 +294,7 @@ public class SplitPdfByChaptersController {
splitDocumentsBoas.add(baos); splitDocumentsBoas.add(baos);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed splitting documents and saving them", e); ExceptionUtils.logException("document splitting and saving", e);
throw e; throw e;
} }
} }

View File

@ -105,7 +105,7 @@ public class SplitPdfBySizeController {
log.debug("PDF splitting completed successfully"); log.debug("PDF splitting completed successfully");
} catch (Exception e) { } catch (Exception e) {
log.error("Error loading or processing PDF document", e); ExceptionUtils.logException("PDF document loading or processing", e);
throw e; throw e;
} }
} catch (IOException e) { } catch (IOException e) {
@ -114,7 +114,7 @@ public class SplitPdfBySizeController {
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Exception during PDF splitting process", e); ExceptionUtils.logException("PDF splitting process", e);
throw e; // Re-throw to ensure proper error response throw e; // Re-throw to ensure proper error response
} finally { } finally {
try { try {
@ -278,7 +278,7 @@ public class SplitPdfBySizeController {
currentDoc = pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); currentDoc = pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
log.debug("Successfully created initial output document"); log.debug("Successfully created initial output document");
} catch (Exception e) { } catch (Exception e) {
log.error("Error creating initial output document", e); ExceptionUtils.logException("initial output document creation", e);
throw ExceptionUtils.createFileProcessingException("split", e); throw ExceptionUtils.createFileProcessingException("split", e);
} }

View File

@ -606,7 +606,7 @@ public class CompressController {
return "empty-stream"; return "empty-stream";
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Error generating image hash", e); ExceptionUtils.logException("image hash generation", e);
return "fallback-" + System.identityHashCode(image); return "fallback-" + System.identityHashCode(image);
} }
} }

View File

@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils; import stirling.software.common.util.WebResponseUtils;
@RestController @RestController
@ -134,7 +135,7 @@ public class DecompressPdfController {
stream.setInt(COSName.LENGTH, decompressedBytes.length); stream.setInt(COSName.LENGTH, decompressedBytes.length);
} }
} catch (IOException e) { } catch (IOException e) {
log.error("Error decompressing stream", e); ExceptionUtils.logException("stream decompression", e);
// Continue processing other streams even if this one fails // Continue processing other streams even if this one fails
} }
} }

View File

@ -91,39 +91,54 @@ public class ExtractImagesController {
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Set<Future<Void>> futures = new HashSet<>(); Set<Future<Void>> futures = new HashSet<>();
// Iterate over each page // Safely iterate over each page, handling corrupt PDFs where page count might be wrong
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) { try {
PDPage page = document.getPage(pgNum); int pageCount = document.getPages().getCount();
Future<Void> future = log.debug("Document reports {} pages", pageCount);
executor.submit(
() -> {
// Use the page number directly from the iterator, so no need to
// calculate manually
int pageNum = document.getPages().indexOf(page) + 1;
try { int consecutiveFailures = 0;
// Call the image extraction method for each page
extractImagesFromPage(
page,
format,
filename,
pageNum,
processedImages,
zos,
allowDuplicates);
} catch (IOException e) {
// Log the error and continue processing other pages
log.error(
"Error extracting images from page {}: {}",
pageNum,
e.getMessage());
}
return null; // Callable requires a return type for (int pgNum = 0; pgNum < pageCount; pgNum++) {
}); try {
PDPage page = document.getPage(pgNum);
consecutiveFailures = 0; // Reset on success
final int currentPageNum = pgNum + 1; // Convert to 1-based page numbering
Future<Void> future =
executor.submit(
() -> {
try {
// Call the image extraction method for each page
extractImagesFromPage(
page,
format,
filename,
currentPageNum,
processedImages,
zos,
allowDuplicates);
} catch (Exception e) {
// Log the error and continue processing other pages
ExceptionUtils.logException("image extraction from page " + currentPageNum, e);
}
// Add the Future object to the list to track completion return null; // Callable requires a return type
futures.add(future); });
// Add the Future object to the list to track completion
futures.add(future);
} catch (Exception e) {
consecutiveFailures++;
ExceptionUtils.logException("page access for page " + (pgNum + 1), e);
if (consecutiveFailures >= 3) {
log.warn("Stopping page iteration after 3 consecutive failures");
break;
}
}
}
} catch (Exception e) {
ExceptionUtils.logException("page count determination", e);
throw e;
} }
// Wait for all tasks to complete // Wait for all tasks to complete

View File

@ -150,11 +150,18 @@ public class PipelineProcessor {
} }
} }
if (!hasInputFileType) { if (!hasInputFileType) {
String filename = file.getFilename();
String providedExtension = "no extension";
if (filename != null && filename.contains(".")) {
providedExtension = filename.substring(filename.lastIndexOf(".")).toLowerCase();
}
logPrintStream.println( logPrintStream.println(
"No files with extension " "No files with extension "
+ String.join(", ", inputFileTypes) + String.join(", ", inputFileTypes)
+ " found for operation " + " found for operation "
+ operation); + operation
+ ". Provided file '" + filename + "' has extension: " + providedExtension);
hasErrors = true; hasErrors = true;
} }
} }
@ -203,11 +210,26 @@ public class PipelineProcessor {
hasErrors = true; hasErrors = true;
} }
} else { } else {
// Get details about what files were actually provided
List<String> providedExtensions = outputFiles.stream()
.map(file -> {
String filename = file.getFilename();
if (filename != null && filename.contains(".")) {
return filename.substring(filename.lastIndexOf(".")).toLowerCase();
}
return "no extension";
})
.distinct()
.toList();
logPrintStream.println( logPrintStream.println(
"No files with extension " "No files with extension "
+ String.join(", ", inputFileTypes) + String.join(", ", inputFileTypes)
+ " found for multi-input operation " + " found for multi-input operation "
+ operation); + operation
+ ". Provided files have extensions: "
+ String.join(", ", providedExtensions)
+ " (total files: " + outputFiles.size() + ")");
hasErrors = true; hasErrors = true;
} }
} }

View File

@ -133,7 +133,7 @@ public class CertSignController {
} }
doc.saveIncremental(output); doc.saveIncremental(output);
} catch (Exception e) { } catch (Exception e) {
log.error("exception", e); ExceptionUtils.logException("PDF signing", e);
} }
} }

View File

@ -63,6 +63,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils; import stirling.software.common.util.WebResponseUtils;
@RestController @RestController
@ -181,7 +182,7 @@ public class GetInfoOnPDF {
} }
} }
} catch (Exception e) { } catch (Exception e) {
log.error("exception", e); ExceptionUtils.logException("PDF standard checking", e);
} }
return false; return false;

View File

@ -77,7 +77,7 @@ public class MetricsAggregatorService {
double lastCount = lastSentMetrics.getOrDefault(key, 0.0); double lastCount = lastSentMetrics.getOrDefault(key, 0.0);
double difference = currentCount - lastCount; double difference = currentCount - lastCount;
if (difference > 0) { if (difference > 0) {
logger.info("{}, {}", key, difference); logger.debug("{}, {}", key, difference);
metrics.put(key, difference); metrics.put(key, difference);
lastSentMetrics.put(key, currentCount); lastSentMetrics.put(key, currentCount);
} }