diff --git a/akka-http-core/src/main/resources/reference.conf b/akka-http-core/src/main/resources/reference.conf index b07f3a5ba6..717c0f77cb 100644 --- a/akka-http-core/src/main/resources/reference.conf +++ b/akka-http-core/src/main/resources/reference.conf @@ -147,8 +147,19 @@ akka.http { # under the HTTP specification even without this header. # If a header cannot be parsed into a high-level model instance it will be # provided as a `RawHeader`. + # If logging is enabled it is performed with the configured + # `error-logging-verbosity`. illegal-header-warnings = on + # Configures the verbosity with which message (request or response) parsing + # errors are written to the application log. + # + # Supported settings: + # `off` : no log messages are produced + # `simple`: a condensed single-line message is logged + # `full` : the full error details (potentially spanning several lines) are logged + error-logging-verbosity = full + # limits for the number of different values per header type that the # header cache will hold header-cache { diff --git a/akka-http-core/src/main/scala/akka/http/engine/client/HttpClient.scala b/akka-http-core/src/main/scala/akka/http/engine/client/HttpClient.scala index bc00dbcd13..f05ba87d68 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/client/HttpClient.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/client/HttpClient.scala @@ -17,7 +17,7 @@ import akka.stream.scaladsl._ import akka.stream.scaladsl.OperationAttributes._ import akka.http.model.{ IllegalResponseException, HttpMethod, HttpRequest, HttpResponse } import akka.http.engine.rendering.{ RequestRenderingContext, HttpRequestRendererFactory } -import akka.http.engine.parsing.{ ParserOutput, HttpHeaderParser, HttpResponseParser } +import akka.http.engine.parsing._ import akka.http.util._ /** @@ -58,11 +58,10 @@ private[http] object HttpClient { // the initial header parser we initially use for every connection, // will not be mutated, all "shared copy" parsers copy on first-write into the header cache - val rootParser = new HttpResponseParser( - parserSettings, - HttpHeaderParser(parserSettings) { errorInfo ⇒ - if (parserSettings.illegalHeaderWarnings) log.warning(errorInfo.withSummaryPrepended("Illegal response header").formatPretty) - }) + val rootParser = new HttpResponseParser(parserSettings, HttpHeaderParser(parserSettings) { info ⇒ + if (parserSettings.illegalHeaderWarnings) + logParsingError(info withSummaryPrepended "Illegal response header", log, parserSettings.errorLoggingVerbosity) + }) val requestRendererFactory = new HttpRequestRendererFactory(userAgentHeader, requestHeaderSizeHint, log) diff --git a/akka-http-core/src/main/scala/akka/http/engine/parsing/ParserSettings.scala b/akka-http-core/src/main/scala/akka/http/engine/parsing/ParserSettings.scala index 34b954fc90..683950234e 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/parsing/ParserSettings.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/parsing/ParserSettings.scala @@ -4,6 +4,7 @@ package akka.http.engine.parsing +import java.util.Locale import com.typesafe.config.Config import scala.collection.JavaConverters._ import akka.http.model.{ StatusCode, HttpMethod, Uri } @@ -21,6 +22,7 @@ final case class ParserSettings( maxChunkSize: Int, uriParsingMode: Uri.ParsingMode, illegalHeaderWarnings: Boolean, + errorLoggingVerbosity: ParserSettings.ErrorLoggingVerbosity, headerValueCacheLimits: Map[String, Int], customMethods: String ⇒ Option[HttpMethod], customStatusCodes: Int ⇒ Option[StatusCode]) extends HttpHeaderParser.Settings { @@ -66,9 +68,25 @@ object ParserSettings extends SettingsCompanion[ParserSettings]("akka.http.parsi c getIntBytes "max-chunk-size", Uri.ParsingMode(c getString "uri-parsing-mode"), c getBoolean "illegal-header-warnings", + ErrorLoggingVerbosity(c getString "error-logging-verbosity"), cacheConfig.entrySet.asScala.map(kvp ⇒ kvp.getKey -> cacheConfig.getInt(kvp.getKey))(collection.breakOut), _ ⇒ None, _ ⇒ None) } + + sealed trait ErrorLoggingVerbosity + object ErrorLoggingVerbosity { + case object Off extends ErrorLoggingVerbosity + case object Simple extends ErrorLoggingVerbosity + case object Full extends ErrorLoggingVerbosity + + def apply(string: String): ErrorLoggingVerbosity = + string.toLowerCase(Locale.ROOT) match { + case "off" ⇒ Off + case "simple" ⇒ Simple + case "full" ⇒ Full + case x ⇒ throw new IllegalArgumentException(s"[$x] is not a legal `error-logging-verbosity` setting") + } + } } diff --git a/akka-http-core/src/main/scala/akka/http/engine/parsing/package.scala b/akka-http-core/src/main/scala/akka/http/engine/parsing/package.scala index f65b10107f..12f6a1cd22 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/parsing/package.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/parsing/package.scala @@ -6,15 +6,16 @@ package akka.http.engine import java.lang.{ StringBuilder ⇒ JStringBuilder } import scala.annotation.tailrec +import akka.event.LoggingAdapter import akka.util.ByteString import akka.http.model.{ ErrorInfo, StatusCode, StatusCodes } import akka.http.util.SingletonException +/** + * INTERNAL API + */ package object parsing { - /** - * INTERNAL API - */ private[http] def escape(c: Char): String = c match { case '\t' ⇒ "\\t" case '\r' ⇒ "\\r" @@ -23,25 +24,24 @@ package object parsing { case x ⇒ x.toString } - /** - * INTERNAL API - */ private[http] def byteChar(input: ByteString, ix: Int): Char = byteAt(input, ix).toChar - /** - * INTERNAL API - */ private[http] def byteAt(input: ByteString, ix: Int): Byte = if (ix < input.length) input(ix) else throw NotEnoughDataException - /** - * INTERNAL API - */ private[http] def asciiString(input: ByteString, start: Int, end: Int): String = { @tailrec def build(ix: Int = start, sb: JStringBuilder = new JStringBuilder(end - start)): String = if (ix == end) sb.toString else build(ix + 1, sb.append(input(ix).toChar)) if (start == end) "" else build() } + + private[http] def logParsingError(info: ErrorInfo, log: LoggingAdapter, + setting: ParserSettings.ErrorLoggingVerbosity): Unit = + setting match { + case ParserSettings.ErrorLoggingVerbosity.Off ⇒ // nothing to do + case ParserSettings.ErrorLoggingVerbosity.Simple ⇒ log.warning(info.summary) + case ParserSettings.ErrorLoggingVerbosity.Full ⇒ log.warning(info.formatPretty) + } } package parsing { diff --git a/akka-http-core/src/main/scala/akka/http/engine/server/HttpServer.scala b/akka-http-core/src/main/scala/akka/http/engine/server/HttpServer.scala index 76e6107e51..1f73d58101 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/server/HttpServer.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/server/HttpServer.scala @@ -13,12 +13,12 @@ import akka.stream.stage.PushPullStage import akka.stream.scaladsl.OperationAttributes._ import akka.stream.scaladsl._ import akka.stream._ -import akka.http.engine.parsing.{ HttpHeaderParser, HttpRequestParser } +import akka.http.engine.parsing._ import akka.http.engine.rendering.{ ResponseRenderingContext, HttpResponseRendererFactory } -import akka.http.engine.parsing.ParserOutput._ import akka.http.engine.TokenSourceActor import akka.http.model._ import akka.http.util._ +import ParserOutput._ /** * INTERNAL API @@ -50,14 +50,14 @@ private[http] object HttpServer { def serverBlueprint(settings: ServerSettings, log: LoggingAdapter)(implicit mat: FlowMaterializer): Graph[HttpServerPorts, Unit] = { + import settings._ // the initial header parser we initially use for every connection, // will not be mutated, all "shared copy" parsers copy on first-write into the header cache - val rootParser = new HttpRequestParser( - settings.parserSettings, - settings.rawRequestUriHeader, - HttpHeaderParser(settings.parserSettings) { errorInfo ⇒ - if (settings.parserSettings.illegalHeaderWarnings) log.warning(errorInfo.withSummaryPrepended("Illegal request header").formatPretty) + val rootParser = new HttpRequestParser(parserSettings, rawRequestUriHeader, + HttpHeaderParser(parserSettings) { info ⇒ + if (parserSettings.illegalHeaderWarnings) + logParsingError(info withSummaryPrepended "Illegal request header", log, parserSettings.errorLoggingVerbosity) }) val responseRendererFactory = new HttpResponseRendererFactory(settings.serverHeader, settings.responseHeaderSizeHint, log) @@ -149,7 +149,7 @@ private[http] object HttpServer { this.requestStart = requestStart ctx.changeCompletionHandling(waitingForApplicationResponseCompletionHandling) waitingForApplicationResponse - case (ctx, _, MessageStartError(status, info)) ⇒ finishWithError(ctx, "request", status, info) + case (ctx, _, MessageStartError(status, info)) ⇒ finishWithError(ctx, status, info) case _ ⇒ throw new IllegalStateException } @@ -181,12 +181,13 @@ private[http] object HttpServer { onUpstreamFailure = { case (ctx, _, EntityStreamException(errorInfo)) ⇒ // the application has forwarded a request entity stream error to the response stream - finishWithError(ctx, "request", StatusCodes.BadRequest, errorInfo) + finishWithError(ctx, StatusCodes.BadRequest, errorInfo) case (ctx, _, error) ⇒ { ctx.fail(error); SameState } }) - def finishWithError(ctx: MergeLogicContextBase, target: String, status: StatusCode, info: ErrorInfo): State[Any] = { - log.warning("Illegal {}, responding with status '{}': {}", target, status, info.formatPretty) + def finishWithError(ctx: MergeLogicContextBase, status: StatusCode, info: ErrorInfo): State[Any] = { + logParsingError(info withSummaryPrepended s"Illegal request, responding with status '$status'", + log, settings.parserSettings.errorLoggingVerbosity) val msg = if (settings.verboseErrorMessages) info.formatPretty else info.summary // FIXME this is a workaround that is supposed to be solved by issue #16753 ctx match { @@ -195,7 +196,6 @@ private[http] object HttpServer { fullCtx.emit(ResponseRenderingContext(HttpResponse(status, entity = msg), closeRequested = true)) case other ⇒ throw new IllegalStateException(s"Unexpected MergeLogicContext [${other.getClass.getName}]") } - // finish(ctx) }