Skip to content

Commit 9d81260

Browse files
srielaucloud-fan
authored andcommitted
[SPARK-54883][MINOR] Clean up error messages for CLI and add new error mode DEBUG
### What changes were proposed in this pull request? Two changes in this pull request: - Suppress stack trace information fro spark-sql CLI with PRETTY error format, unless the error is internal. Prior to this stack trace was suppressed only for AnalysisException - Introduce a new DEBUG error format which matches PRETTY + always dumping the stack (if available). ### Why are the changes needed? This makes the experience of using CLI much more consistent without losing capability. It also clearly seperates "consumer" output, from developer output. ### Does this PR introduce _any_ user-facing change? Yes, in CLI error message output is by default more consistent. ### How was this patch tested? Added new tests ### Was this patch authored or co-authored using generative AI tooling? Claude Closes #53659 from srielau/spark-sql. Authored-by: Serge Rielau <serge@rielau.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com>
1 parent 20de5e7 commit 9d81260

5 files changed

Lines changed: 59 additions & 16 deletions

File tree

common/utils/src/main/scala/org/apache/spark/SparkThrowableHelper.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import org.apache.spark.util.JsonUtils.toJsonString
2323
import org.apache.spark.util.SparkClassUtils
2424

2525
private[spark] object ErrorMessageFormat extends Enumeration {
26-
val PRETTY, MINIMAL, STANDARD = Value
26+
val PRETTY, MINIMAL, STANDARD, DEBUG = Value
2727
}
2828

2929
/**
@@ -113,7 +113,7 @@ private[spark] object SparkThrowableHelper {
113113
def getMessage(e: SparkThrowable with Throwable, format: ErrorMessageFormat.Value): String = {
114114
import ErrorMessageFormat._
115115
format match {
116-
case PRETTY => e.getMessage
116+
case PRETTY | DEBUG => e.getMessage
117117
case MINIMAL | STANDARD if e.getCondition == null =>
118118
toJsonString { generator =>
119119
val g = generator.useDefaultPrettyPrinter()

project/MimaExcludes.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ object MimaExcludes {
3535

3636
// Exclude rules for 4.2.x from 4.1.0
3737
lazy val v42excludes = v41excludes ++ Seq(
38+
// Add DEBUG format to ErrorMessageFormat enum
39+
ProblemFilters.exclude[Problem]("org.apache.spark.ErrorMessageFormat*")
3840
)
3941

4042
// Exclude rules for 4.1.x from 4.0.0

sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6193,7 +6193,9 @@ object SQLConf {
61936193

61946194
val ERROR_MESSAGE_FORMAT = buildConf("spark.sql.error.messageFormat")
61956195
.doc("When PRETTY, the error message consists of textual representation of error class, " +
6196-
"message and query context. The MINIMAL and STANDARD formats are pretty JSON formats where " +
6196+
"message and query context. Stack traces are only shown for internal errors " +
6197+
"(SQLSTATE XX***). When DEBUG, the output is the same as PRETTY but stack traces are " +
6198+
"always included. The MINIMAL and STANDARD formats are pretty JSON formats where " +
61976199
"STANDARD includes an additional JSON field `message`. This configuration property " +
61986200
"influences on error messages of Thrift Server and SQL CLI while running queries.")
61996201
.version("3.4.0")

sql/hive-thriftserver/src/main/scala/org/apache/spark/sql/hive/thriftserver/SparkSQLCLIDriver.scala

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import org.apache.spark.{ErrorMessageFormat, SparkConf, SparkThrowable, SparkThr
4040
import org.apache.spark.deploy.SparkHadoopUtil
4141
import org.apache.spark.internal.Logging
4242
import org.apache.spark.internal.LogKeys._
43-
import org.apache.spark.sql.AnalysisException
4443
import org.apache.spark.sql.catalyst.analysis.FunctionRegistry
4544
import org.apache.spark.sql.catalyst.util.SQLKeywordUtils
4645
import org.apache.spark.sql.hive.client.HiveClientImpl
@@ -452,9 +451,24 @@ private[hive] class SparkSQLCLIDriver extends CliDriver with Logging {
452451
case _ => t.getMessage
453452
}
454453
err.println(msg)
455-
if (format == ErrorMessageFormat.PRETTY &&
456-
!sessionState.getIsSilent &&
457-
(!t.isInstanceOf[AnalysisException] || t.getCause != null)) {
454+
// Print stack traces based on format and error type:
455+
// - DEBUG format: Always print stack traces (for debugging)
456+
// - PRETTY format: Only for internal errors (SQLSTATE XX***)
457+
// - MINIMAL/STANDARD formats: Never print stack traces (JSON only)
458+
val shouldPrintStackTrace = format match {
459+
case ErrorMessageFormat.DEBUG => true // Always print in DEBUG mode
460+
case ErrorMessageFormat.PRETTY =>
461+
// In PRETTY mode, only print for internal errors
462+
t match {
463+
case st: SparkThrowable =>
464+
val sqlState = st.getSqlState
465+
// Print if: internal error (XX***) OR no SQLSTATE
466+
sqlState == null || sqlState.startsWith("XX")
467+
case _ => true // Non-SparkThrowable exceptions always get stack traces
468+
}
469+
case _ => false // MINIMAL and STANDARD never print stack traces
470+
}
471+
if (shouldPrintStackTrace && !sessionState.getIsSilent) {
458472
t.printStackTrace(err)
459473
}
460474
driver.close()
@@ -688,4 +702,3 @@ private[hive] class SparkSQLCLIDriver extends CliDriver with Logging {
688702
ret
689703
}
690704
}
691-

sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/CliSuite.scala

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -554,22 +554,26 @@ class CliSuite extends SparkFunSuite {
554554
)
555555
}
556556

557-
testRetry("SparkException with root cause will be printStacktrace") {
558-
// If it is not in silent mode, will print the stacktrace
557+
testRetry("DEBUG format prints stack traces for all errors") {
558+
// In DEBUG format with silent=false, stack traces are always printed
559559
runCliWithin(
560560
1.minute,
561-
extraArgs = Seq("--hiveconf", "hive.session.silent=false",
561+
extraArgs = Seq(
562+
"--hiveconf", "hive.session.silent=false",
563+
"--conf", s"${SQLConf.ERROR_MESSAGE_FORMAT.key}=${ErrorMessageFormat.DEBUG}",
562564
"-e", "select from_json('a', 'a INT', map('mode', 'FAILFAST'));"),
563565
errorResponses = Seq("JsonParseException"))(
564-
("", "SparkException: [MALFORMED_RECORD_IN_PARSING.WITHOUT_SUGGESTION]"),
566+
("", "[MALFORMED_RECORD_IN_PARSING.WITHOUT_SUGGESTION]"),
565567
("", "JsonParseException: Unrecognized token 'a'"))
566-
// If it is in silent mode, will print the error message only
568+
// In DEBUG format with silent=true, stack traces are suppressed
567569
runCliWithin(
568570
1.minute,
569-
extraArgs = Seq("--conf", "spark.hive.session.silent=true",
571+
extraArgs = Seq(
572+
"--conf", "spark.hive.session.silent=true",
573+
"--conf", s"${SQLConf.ERROR_MESSAGE_FORMAT.key}=${ErrorMessageFormat.DEBUG}",
570574
"-e", "select from_json('a', 'a INT', map('mode', 'FAILFAST'));"),
571-
errorResponses = Seq("SparkException"))(
572-
("", "SparkException: [MALFORMED_RECORD_IN_PARSING.WITHOUT_SUGGESTION]"))
575+
errorResponses = Seq("MALFORMED_RECORD_IN_PARSING"))(
576+
("", "[MALFORMED_RECORD_IN_PARSING.WITHOUT_SUGGESTION]"))
573577
}
574578

575579
test("SPARK-30808: use Java 8 time API in Thrift SQL CLI by default") {
@@ -714,6 +718,8 @@ class CliSuite extends SparkFunSuite {
714718
"-e", "select 1 / 0"),
715719
errorResponses = Seq("DIVIDE_BY_ZERO"))(expected.toImmutableArraySeq: _*)
716720
}
721+
// DIVIDE_BY_ZERO has SQLSTATE 22012 (not XX***), so it's a user error
722+
// It also has no cause, so stack traces should NOT be shown in PRETTY mode
717723
check(
718724
format = ErrorMessageFormat.PRETTY,
719725
errorMessage =
@@ -725,6 +731,26 @@ class CliSuite extends SparkFunSuite {
725731
silent = true)
726732
check(
727733
format = ErrorMessageFormat.PRETTY,
734+
errorMessage =
735+
"""[DIVIDE_BY_ZERO] Division by zero. Use `try_divide` to tolerate divisor being 0 and return NULL instead. If necessary set "spark.sql.ansi.enabled" to "false" to bypass this error.
736+
|== SQL (line 1, position 8) ==
737+
|select 1 / 0
738+
| ^^^^^
739+
|""".stripMargin,
740+
silent = false)
741+
// DEBUG format should always show stack traces, even for user errors
742+
// Silent mode still suppresses stack traces
743+
check(
744+
format = ErrorMessageFormat.DEBUG,
745+
errorMessage =
746+
"""[DIVIDE_BY_ZERO] Division by zero. Use `try_divide` to tolerate divisor being 0 and return NULL instead. If necessary set "spark.sql.ansi.enabled" to "false" to bypass this error.
747+
|== SQL (line 1, position 8) ==
748+
|select 1 / 0
749+
| ^^^^^
750+
|""".stripMargin,
751+
silent = true)
752+
check(
753+
format = ErrorMessageFormat.DEBUG,
728754
errorMessage =
729755
"""[DIVIDE_BY_ZERO] Division by zero. Use `try_divide` to tolerate divisor being 0 and return NULL instead. If necessary set "spark.sql.ansi.enabled" to "false" to bypass this error.
730756
|== SQL (line 1, position 8) ==

0 commit comments

Comments
 (0)