=htc #16807 introduce akka.http.parsing.error-logging-verbosity setting

This commit is contained in:
Mathias 2015-03-30 15:26:27 +02:00
parent 37aa2cb886
commit 14399f71f5
5 changed files with 58 additions and 30 deletions

View file

@ -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 {

View file

@ -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,10 +58,9 @@ 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)

View file

@ -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")
}
}
}

View file

@ -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
package object parsing {
/**
/**
* INTERNAL API
*/
package object parsing {
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 {

View file

@ -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)
}