-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Adds logging for every request to ksqlDB #6615
feat: Adds logging for every request to ksqlDB #6615
Conversation
Hi Alan, vertx-web does have the concept of a "filter" - that's basically what a handler is. Handlers can do stuff to a request, and decided whether or not to forward to the next handler. They can also do stuff when the request is complete - by setting end handlers on the routing context, (i.e. ctx.addEndHandler(), ctx.addBodyEndHandler(), ctx.addHeadersEndHandler()). So you can end up with something like this:
And then you just add your "filter" at the beginning of your handler chain |
Hi @purplefox . Thanks for the feedback. I was going to reach out to you, but glad you found me first. I hadn't noticed the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @AlanConfluent -- bunch of comments inline but LGTM overall!
try { | ||
return Integer.parseInt(responseCode); | ||
} catch (NumberFormatException e) { | ||
throw new IllegalStateException("Configured bad response code " + responseCode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a validator on the config to ensure that the config parses properly? Then we can throw a ConfigException rather than IllegalStateException.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Added validator. It's somewhat irritating that you can't actually do the parsing as well as part of the validation stage because I effectively have to parse again.
|
||
@Override | ||
public void handle(final RoutingContext routingContext) { | ||
// If we wanted to log at the beginning of a request, it would go here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this comment adds anything -- I was also confused the first time I read it since I thought it was describing the line below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Removed.
if (routingContext.response().getStatusCode() > 300) { | ||
errorMessage = routingContext.response().getStatusMessage(); | ||
if (Strings.isNullOrEmpty(errorMessage)) { | ||
errorMessage = routingContext.getBodyAsString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of this? Why would we have a situation where an erroneous response code was returned with no message, and why would we want to log the body in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was looking through the code and though it was possible to not set the status message. Either way, in conforming to the Apache log format, I'm just going to remove this.
"Request complete - %s %s status: %d, user agent: %s, request body: %d bytes," | ||
+ " error response: %s", | ||
routingContext.request().remoteAddress().host(), | ||
routingContext.request().uri(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also add the HTTP method. (We used to have Jetty HTTP request logging. This is the information we had at the time: https://www.eclipse.org/jetty/documentation/current/configuring-jetty-request-logs.html#constructing-request-log-entry)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I kept a mostly apache like log message with the addition of request body bytes.
routingContext.request().uri(), | ||
routingContext.response().getStatusCode(), | ||
routingContext.request().getHeader(HTTP_HEADER_USER_AGENT), | ||
routingContext.request().bytesRead(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just the size of the request body, or does it include the size of the headers as well? It'd be nice to have both. At minimum we should understand which this is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just the body. There's no API I've seen for the header size, though we do have access to the headers. I was going to try to estimate the size, by looking at java objects, since from all of my research they're always ASCII, but this seems a bit hacky. I can try that out if we really want that.
@@ -319,6 +319,11 @@ | |||
"The key store certificate alias to be used for internal client requests. If not set, " | |||
+ "the system will fall back on the Vert.x default choice"; | |||
|
|||
public static final String KSQL_LOGGING_SKIP_RESPONSE_CODES_CONFIG = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add docs for this new config?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
public static final String KSQL_LOGGING_SKIP_RESPONSE_CODES_CONFIG = | ||
KSQL_CONFIG_PREFIX + "logging.skip.response.codes"; | ||
private static final String KSQL_LOGGING_SKIP_RESPONSE_CODES_DOC = | ||
"A list of response codes to skip logging"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
"A list of response codes to skip logging"; | |
"A list of HTTP response codes to skip during server request logging"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
ksqldb-rest-app/src/test/java/io/confluent/ksql/api/server/LoggingHandlerTest.java
Show resolved
Hide resolved
ksqldb-rest-app/src/test/java/io/confluent/ksql/api/server/LoggingHandlerTest.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was worry that pull queries could introduce too much logging if done at high qps, so I introduced a rate limiter to the logger.
try { | ||
return Integer.parseInt(responseCode); | ||
} catch (NumberFormatException e) { | ||
throw new IllegalStateException("Configured bad response code " + responseCode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Added validator. It's somewhat irritating that you can't actually do the parsing as well as part of the validation stage because I effectively have to parse again.
|
||
@Override | ||
public void handle(final RoutingContext routingContext) { | ||
// If we wanted to log at the beginning of a request, it would go here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Removed.
if (routingContext.response().getStatusCode() > 300) { | ||
errorMessage = routingContext.response().getStatusMessage(); | ||
if (Strings.isNullOrEmpty(errorMessage)) { | ||
errorMessage = routingContext.getBodyAsString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was looking through the code and though it was possible to not set the status message. Either way, in conforming to the Apache log format, I'm just going to remove this.
"Request complete - %s %s status: %d, user agent: %s, request body: %d bytes," | ||
+ " error response: %s", | ||
routingContext.request().remoteAddress().host(), | ||
routingContext.request().uri(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I kept a mostly apache like log message with the addition of request body bytes.
routingContext.request().uri(), | ||
routingContext.response().getStatusCode(), | ||
routingContext.request().getHeader(HTTP_HEADER_USER_AGENT), | ||
routingContext.request().bytesRead(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just the body. There's no API I've seen for the header size, though we do have access to the headers. I was going to try to estimate the size, by looking at java objects, since from all of my research they're always ASCII, but this seems a bit hacky. I can try that out if we really want that.
public static final String KSQL_LOGGING_SKIP_RESPONSE_CODES_CONFIG = | ||
KSQL_CONFIG_PREFIX + "logging.skip.response.codes"; | ||
private static final String KSQL_LOGGING_SKIP_RESPONSE_CODES_DOC = | ||
"A list of response codes to skip logging"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -319,6 +319,11 @@ | |||
"The key store certificate alias to be used for internal client requests. If not set, " | |||
+ "the system will fall back on the Vert.x default choice"; | |||
|
|||
public static final String KSQL_LOGGING_SKIP_RESPONSE_CODES_CONFIG = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
ksqldb-rest-app/src/test/java/io/confluent/ksql/api/server/LoggingHandlerTest.java
Show resolved
Hide resolved
docs/operate-and-deploy/installation/server-config/config-reference.md
Outdated
Show resolved
Hide resolved
docs/operate-and-deploy/installation/server-config/config-reference.md
Outdated
Show resolved
Hide resolved
docs/operate-and-deploy/installation/server-config/config-reference.md
Outdated
Show resolved
Hide resolved
Co-authored-by: Jim Galasyn <jim.galasyn@confluent.io>
docs/operate-and-deploy/installation/server-config/config-reference.md
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, with a couple of suggestions.
Co-authored-by: Jim Galasyn <jim.galasyn@confluent.io>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @AlanConfluent . Awesome stuff!
docs/operate-and-deploy/installation/server-config/config-reference.md
Outdated
Show resolved
Hide resolved
private static final String KSQL_LOGGING_SKIP_RESPONSE_CODES_DOC = | ||
"A list of response codes to skip logging"; | ||
public static final String KSQL_LOGGING_SKIPPED_RESPONSE_CODES_CONFIG = | ||
KSQL_CONFIG_PREFIX + "logging.skipped.response.codes"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this ksql.logging.server.skipped.response.codes
or similar to indicate that this is only for server request logging, in contrast to the ksql.logging.processing.*
configs for the processing log? I considered suggesting ksql.logging.request.*
rather than ksql.logging.server.*
but I worry that a config such as ksql.logging.request.skipped.response.codes
could be confusing. Maybe it's OK and that's actually better, unsure.
If we change the config name, we'll also have to update the docs. (Thanks for adding those!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose ksql.logging.server.*
since I agree it's confusing if you have both request
and response
in the same name.
I also updated the docs I added as well.
"A list of HTTP response codes to skip during server request logging"; | ||
|
||
public static final String KSQL_LOGGING_RATE_LIMITED_REQUEST_PATHS_CONFIG = | ||
KSQL_CONFIG_PREFIX + "logging.rate.limited.request.paths"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above regarding clarifying that this a server request logging config, in contrast to other types of logging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -914,4 +930,8 @@ private static String getLocalHostName() { | |||
} | |||
} | |||
|
|||
public Map<String, String> getStringAsMap(final String key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks unused?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is called in LoggingHandler.getSkipRequestPaths()
to get the map of rate limits. I also called KsqlConfig.parseStringAsMap
in the validator, so it's being parsed twice. It would be nice to be able to parse and validate at once, but it doesn't seem like the config library supports this at the moment.
routingContext.request().getHeader(HTTP_HEADER_USER_AGENT)).orElse("-"); | ||
final String timestamp = Utils.formatRFC1123DateTime(clock.millis()); | ||
final String message = String.format( | ||
"%s - %s [%s] \"%s %s %s\" %d %d \"-\" \"%s\" %d", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to add a short docs topic on the meaning of this format, since there are adaptations from the standard Apache log format?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's mixed with the other debug statements and the format may change if we get additional requested fields, it doesn't seem like should commit to a particular format for the moment. I'm happy to write up a short doc explaining the format if this ends up being what we stick with.
@@ -117,7 +117,7 @@ public synchronized void start() { | |||
final List<URI> allListenUris = new ArrayList<>(listenUris); | |||
internalListenUri.ifPresent(allListenUris::add); | |||
|
|||
final int instances = config.getInt(KsqlRestConfig.VERTICLE_INSTANCES); | |||
final int instances = config.getInt(KsqlRestConfig.VERTICLE_INSTANCES); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: spacing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I think this was an accidental addition of whitespace!
this.logger = logger; | ||
this.clock = clock; | ||
this.rateLimiterFactory = rateLimiterFactory; | ||
} | ||
|
||
private static Set<Integer> getSkipResponseCodes(final KsqlRestConfig config) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: move private helper methods to end of file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
ksqldb-rest-app/src/main/java/io/confluent/ksql/api/server/LoggingHandler.java
Outdated
Show resolved
Hide resolved
Co-authored-by: Victoria Xia <victoria.f.xia281@gmail.com>
Description
Adds logging to every request that comes in with standard fields so it's easier to monitor incoming traffic.
Testing done
Ran unit tests and manually tested.
Reviewer checklist