=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. # under the HTTP specification even without this header.
# If a header cannot be parsed into a high-level model instance it will be # If a header cannot be parsed into a high-level model instance it will be
# provided as a `RawHeader`. # provided as a `RawHeader`.
# If logging is enabled it is performed with the configured
# `error-logging-verbosity`.
illegal-header-warnings = on 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 # limits for the number of different values per header type that the
# header cache will hold # header cache will hold
header-cache { header-cache {

View file

@ -17,7 +17,7 @@ import akka.stream.scaladsl._
import akka.stream.scaladsl.OperationAttributes._ import akka.stream.scaladsl.OperationAttributes._
import akka.http.model.{ IllegalResponseException, HttpMethod, HttpRequest, HttpResponse } import akka.http.model.{ IllegalResponseException, HttpMethod, HttpRequest, HttpResponse }
import akka.http.engine.rendering.{ RequestRenderingContext, HttpRequestRendererFactory } import akka.http.engine.rendering.{ RequestRenderingContext, HttpRequestRendererFactory }
import akka.http.engine.parsing.{ ParserOutput, HttpHeaderParser, HttpResponseParser } import akka.http.engine.parsing._
import akka.http.util._ import akka.http.util._
/** /**
@ -58,10 +58,9 @@ private[http] object HttpClient {
// the initial header parser we initially use for every connection, // 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 // will not be mutated, all "shared copy" parsers copy on first-write into the header cache
val rootParser = new HttpResponseParser( val rootParser = new HttpResponseParser(parserSettings, HttpHeaderParser(parserSettings) { info
parserSettings, if (parserSettings.illegalHeaderWarnings)
HttpHeaderParser(parserSettings) { errorInfo logParsingError(info withSummaryPrepended "Illegal response header", log, parserSettings.errorLoggingVerbosity)
if (parserSettings.illegalHeaderWarnings) log.warning(errorInfo.withSummaryPrepended("Illegal response header").formatPretty)
}) })
val requestRendererFactory = new HttpRequestRendererFactory(userAgentHeader, requestHeaderSizeHint, log) val requestRendererFactory = new HttpRequestRendererFactory(userAgentHeader, requestHeaderSizeHint, log)

View file

@ -4,6 +4,7 @@
package akka.http.engine.parsing package akka.http.engine.parsing
import java.util.Locale
import com.typesafe.config.Config import com.typesafe.config.Config
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import akka.http.model.{ StatusCode, HttpMethod, Uri } import akka.http.model.{ StatusCode, HttpMethod, Uri }
@ -21,6 +22,7 @@ final case class ParserSettings(
maxChunkSize: Int, maxChunkSize: Int,
uriParsingMode: Uri.ParsingMode, uriParsingMode: Uri.ParsingMode,
illegalHeaderWarnings: Boolean, illegalHeaderWarnings: Boolean,
errorLoggingVerbosity: ParserSettings.ErrorLoggingVerbosity,
headerValueCacheLimits: Map[String, Int], headerValueCacheLimits: Map[String, Int],
customMethods: String Option[HttpMethod], customMethods: String Option[HttpMethod],
customStatusCodes: Int Option[StatusCode]) extends HttpHeaderParser.Settings { customStatusCodes: Int Option[StatusCode]) extends HttpHeaderParser.Settings {
@ -66,9 +68,25 @@ object ParserSettings extends SettingsCompanion[ParserSettings]("akka.http.parsi
c getIntBytes "max-chunk-size", c getIntBytes "max-chunk-size",
Uri.ParsingMode(c getString "uri-parsing-mode"), Uri.ParsingMode(c getString "uri-parsing-mode"),
c getBoolean "illegal-header-warnings", c getBoolean "illegal-header-warnings",
ErrorLoggingVerbosity(c getString "error-logging-verbosity"),
cacheConfig.entrySet.asScala.map(kvp kvp.getKey -> cacheConfig.getInt(kvp.getKey))(collection.breakOut), cacheConfig.entrySet.asScala.map(kvp kvp.getKey -> cacheConfig.getInt(kvp.getKey))(collection.breakOut),
_ None, _ None,
_ 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 java.lang.{ StringBuilder JStringBuilder }
import scala.annotation.tailrec import scala.annotation.tailrec
import akka.event.LoggingAdapter
import akka.util.ByteString import akka.util.ByteString
import akka.http.model.{ ErrorInfo, StatusCode, StatusCodes } import akka.http.model.{ ErrorInfo, StatusCode, StatusCodes }
import akka.http.util.SingletonException import akka.http.util.SingletonException
package object parsing { /**
/**
* INTERNAL API * INTERNAL API
*/ */
package object parsing {
private[http] def escape(c: Char): String = c match { private[http] def escape(c: Char): String = c match {
case '\t' "\\t" case '\t' "\\t"
case '\r' "\\r" case '\r' "\\r"
@ -23,25 +24,24 @@ package object parsing {
case x x.toString case x x.toString
} }
/**
* INTERNAL API
*/
private[http] def byteChar(input: ByteString, ix: Int): Char = byteAt(input, ix).toChar private[http] def byteChar(input: ByteString, ix: Int): Char = byteAt(input, ix).toChar
/**
* INTERNAL API
*/
private[http] def byteAt(input: ByteString, ix: Int): Byte = private[http] def byteAt(input: ByteString, ix: Int): Byte =
if (ix < input.length) input(ix) else throw NotEnoughDataException if (ix < input.length) input(ix) else throw NotEnoughDataException
/**
* INTERNAL API
*/
private[http] def asciiString(input: ByteString, start: Int, end: Int): String = { private[http] def asciiString(input: ByteString, start: Int, end: Int): String = {
@tailrec def build(ix: Int = start, sb: JStringBuilder = new JStringBuilder(end - start)): 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 (ix == end) sb.toString else build(ix + 1, sb.append(input(ix).toChar))
if (start == end) "" else build() 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 { package parsing {

View file

@ -13,12 +13,12 @@ import akka.stream.stage.PushPullStage
import akka.stream.scaladsl.OperationAttributes._ import akka.stream.scaladsl.OperationAttributes._
import akka.stream.scaladsl._ import akka.stream.scaladsl._
import akka.stream._ 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.rendering.{ ResponseRenderingContext, HttpResponseRendererFactory }
import akka.http.engine.parsing.ParserOutput._
import akka.http.engine.TokenSourceActor import akka.http.engine.TokenSourceActor
import akka.http.model._ import akka.http.model._
import akka.http.util._ import akka.http.util._
import ParserOutput._
/** /**
* INTERNAL API * INTERNAL API
@ -50,14 +50,14 @@ private[http] object HttpServer {
def serverBlueprint(settings: ServerSettings, def serverBlueprint(settings: ServerSettings,
log: LoggingAdapter)(implicit mat: FlowMaterializer): Graph[HttpServerPorts, Unit] = { log: LoggingAdapter)(implicit mat: FlowMaterializer): Graph[HttpServerPorts, Unit] = {
import settings._
// the initial header parser we initially use for every connection, // 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 // will not be mutated, all "shared copy" parsers copy on first-write into the header cache
val rootParser = new HttpRequestParser( val rootParser = new HttpRequestParser(parserSettings, rawRequestUriHeader,
settings.parserSettings, HttpHeaderParser(parserSettings) { info
settings.rawRequestUriHeader, if (parserSettings.illegalHeaderWarnings)
HttpHeaderParser(settings.parserSettings) { errorInfo logParsingError(info withSummaryPrepended "Illegal request header", log, parserSettings.errorLoggingVerbosity)
if (settings.parserSettings.illegalHeaderWarnings) log.warning(errorInfo.withSummaryPrepended("Illegal request header").formatPretty)
}) })
val responseRendererFactory = new HttpResponseRendererFactory(settings.serverHeader, settings.responseHeaderSizeHint, log) val responseRendererFactory = new HttpResponseRendererFactory(settings.serverHeader, settings.responseHeaderSizeHint, log)
@ -149,7 +149,7 @@ private[http] object HttpServer {
this.requestStart = requestStart this.requestStart = requestStart
ctx.changeCompletionHandling(waitingForApplicationResponseCompletionHandling) ctx.changeCompletionHandling(waitingForApplicationResponseCompletionHandling)
waitingForApplicationResponse waitingForApplicationResponse
case (ctx, _, MessageStartError(status, info)) finishWithError(ctx, "request", status, info) case (ctx, _, MessageStartError(status, info)) finishWithError(ctx, status, info)
case _ throw new IllegalStateException case _ throw new IllegalStateException
} }
@ -181,12 +181,13 @@ private[http] object HttpServer {
onUpstreamFailure = { onUpstreamFailure = {
case (ctx, _, EntityStreamException(errorInfo)) case (ctx, _, EntityStreamException(errorInfo))
// the application has forwarded a request entity stream error to the response stream // 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 } case (ctx, _, error) { ctx.fail(error); SameState }
}) })
def finishWithError(ctx: MergeLogicContextBase, target: String, status: StatusCode, info: ErrorInfo): State[Any] = { def finishWithError(ctx: MergeLogicContextBase, status: StatusCode, info: ErrorInfo): State[Any] = {
log.warning("Illegal {}, responding with status '{}': {}", target, status, info.formatPretty) logParsingError(info withSummaryPrepended s"Illegal request, responding with status '$status'",
log, settings.parserSettings.errorLoggingVerbosity)
val msg = if (settings.verboseErrorMessages) info.formatPretty else info.summary val msg = if (settings.verboseErrorMessages) info.formatPretty else info.summary
// FIXME this is a workaround that is supposed to be solved by issue #16753 // FIXME this is a workaround that is supposed to be solved by issue #16753
ctx match { ctx match {
@ -195,7 +196,6 @@ private[http] object HttpServer {
fullCtx.emit(ResponseRenderingContext(HttpResponse(status, entity = msg), closeRequested = true)) fullCtx.emit(ResponseRenderingContext(HttpResponse(status, entity = msg), closeRequested = true))
case other throw new IllegalStateException(s"Unexpected MergeLogicContext [${other.getClass.getName}]") case other throw new IllegalStateException(s"Unexpected MergeLogicContext [${other.getClass.getName}]")
} }
//
finish(ctx) finish(ctx)
} }