com.datastax.driver.core.QueryLogger Maven / Gradle / Ivy
/*
* Copyright DataStax, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.datastax.driver.core;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import com.datastax.driver.core.querybuilder.BuiltStatement;
import com.google.common.annotations.VisibleForTesting;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A configurable {@link LatencyTracker} that logs all executed statements.
*
* Typically, client applications would instantiate one single query logger (using its {@link
* Builder}), configure it and register it on the relevant {@link Cluster} instance, e.g.:
*
*
*
*
* Cluster cluster = ...
* QueryLogger queryLogger = QueryLogger.builder()
* .withConstantThreshold(...)
* .withMaxQueryStringLength(...)
* .build();
* cluster.register(queryLogger);
*
*
* Refer to the {@link Builder} documentation for more information on configuration settings for
* the query logger.
*
*
Once registered, the query logger will log every {@link RegularStatement}, {@link
* BoundStatement} or {@link BatchStatement} executed by the driver; note that it will never log
* other types of statement, null statements nor any special statement used internally by the
* driver.
*
*
There is one log for each request to a Cassandra node; because the driver sometimes retries
* the same statement on multiple nodes, a single statement execution (for example, a single call to
* {@link Session#execute(Statement)}) can produce multiple logs on different nodes.
*
*
For more flexibility, the query logger uses 3 different {@link Logger} instances:
*
*
*
*
* - {@link #NORMAL_LOGGER}: used to log normal queries, i.e., queries that completed
* successfully within a configurable threshold in milliseconds.
*
- {@link #SLOW_LOGGER}: used to log slow queries, i.e., queries that completed successfully
* but that took longer than a configurable threshold in milliseconds to complete.
*
- {@link #ERROR_LOGGER}: used to log unsuccessful queries, i.e., queries that did not
* completed normally and threw an exception. Note this this logger will also print the full
* stack trace of the reported exception.
*
*
*
*
*
The appropriate logger is chosen according to the following algorithm:
*
*
* - if an exception has been thrown: use {@link #ERROR_LOGGER};
*
- otherwise, if the reported latency is greater than the configured threshold in
* milliseconds: use {@link #SLOW_LOGGER};
*
- otherwise, use {@link #NORMAL_LOGGER}.
*
*
*
*
*
All loggers are activated by setting their levels to {@code DEBUG} or {@code TRACE} (including
* {@link #ERROR_LOGGER}). If the level is set to {@code TRACE} and the statement being logged is a
* {@link BoundStatement}, then the query parameters (if any) will be logged as well (names and
* actual values).
*
*
*
*
Constant thresholds vs. Dynamic thresholds
*
*
Currently the QueryLogger can track slow queries in two different ways: using a {@link
* Builder#withConstantThreshold(long)} constant threshold} in milliseconds (which is the default
* behavior), or using a {@link Builder#withDynamicThreshold(PercentileTracker, double) dynamic
* threshold} based on latency percentiles.
*
*
This class is thread-safe.
*
* @since 2.0.10
*/
public abstract class QueryLogger implements LatencyTracker {
/**
* The default latency threshold in milliseconds beyond which queries are considered 'slow' and
* logged as such by the driver.
*/
public static final long DEFAULT_SLOW_QUERY_THRESHOLD_MS = 5000;
/**
* The default latency percentile beyond which queries are considered 'slow' and logged as such by
* the driver.
*/
public static final double DEFAULT_SLOW_QUERY_THRESHOLD_PERCENTILE = 99.0;
/**
* The default maximum length of a CQL query string that can be logged verbatim by the driver.
* Query strings longer than this value will be truncated when logged.
*/
public static final int DEFAULT_MAX_QUERY_STRING_LENGTH = 500;
/**
* The default maximum length of a query parameter value that can be logged verbatim by the
* driver. Parameter values longer than this value will be truncated when logged.
*/
public static final int DEFAULT_MAX_PARAMETER_VALUE_LENGTH = 50;
/**
* The default maximum number of query parameters that can be logged by the driver. Queries with a
* number of parameters higher than this value will not have all their parameters logged.
*/
public static final int DEFAULT_MAX_LOGGED_PARAMETERS = 50;
// Loggers
/**
* The logger used to log normal queries, i.e., queries that completed successfully within a
* configurable threshold in milliseconds.
*
*
This logger is activated by setting its level to {@code DEBUG} or {@code TRACE}.
* Additionally, if the level is set to {@code TRACE} and the statement being logged is a {@link
* BoundStatement} or a {@link SimpleStatement}, then the query parameters (if any) will be
* logged. For a {@link BoundStatement} names and actual values are logged and for a {@link
* SimpleStatement} values are logged in positional order and named values are logged with names
* and value.
*
*
The name of this logger is {@code com.datastax.driver.core.QueryLogger.NORMAL}.
*/
public static final Logger NORMAL_LOGGER =
LoggerFactory.getLogger("com.datastax.driver.core.QueryLogger.NORMAL");
/**
* The logger used to log slow queries, i.e., queries that completed successfully but whose
* execution time exceeded a configurable threshold in milliseconds.
*
*
This logger is activated by setting its level to {@code DEBUG} or {@code TRACE}.
* Additionally, if the level is set to {@code TRACE} and the statement being logged is a {@link
* BoundStatement} or a {@link SimpleStatement}, then the query parameters (if any) will be
* logged. For a {@link BoundStatement} names and actual values are logged and for a {@link
* SimpleStatement} values are logged in positional order and named values are logged with names
* and value.
*
*
The name of this logger is {@code com.datastax.driver.core.QueryLogger.SLOW}.
*/
public static final Logger SLOW_LOGGER =
LoggerFactory.getLogger("com.datastax.driver.core.QueryLogger.SLOW");
/**
* The logger used to log unsuccessful queries, i.e., queries that did not complete normally and
* threw an exception.
*
*
This logger is activated by setting its level to {@code DEBUG} or {@code TRACE}.
* Additionally, if the level is set to {@code TRACE} and the statement being logged is a {@link
* BoundStatement} or a {@link SimpleStatement}, then the query parameters (if any) will be
* logged. For a {@link BoundStatement} names and actual values are logged and for a {@link
* SimpleStatement} values are logged in positional order and named values are logged with names
* and value. Note this this logger will also print the full stack trace of the reported
* exception.
*
*
The name of this logger is {@code com.datastax.driver.core.QueryLogger.ERROR}.
*/
public static final Logger ERROR_LOGGER =
LoggerFactory.getLogger("com.datastax.driver.core.QueryLogger.ERROR");
// Message templates
private static final String NORMAL_TEMPLATE =
"[%s] [%s] Query completed normally, took %s ms: %s";
private static final String SLOW_TEMPLATE_MILLIS = "[%s] [%s] Query too slow, took %s ms: %s";
private static final String SLOW_TEMPLATE_PERCENTILE =
"[%s] [%s] Query too slow, took %s ms (%s percentile = %s ms): %s";
private static final String ERROR_TEMPLATE = "[%s] [%s] Query error after %s ms: %s";
@VisibleForTesting static final String TRUNCATED_OUTPUT = "... [truncated output]";
@VisibleForTesting static final String FURTHER_PARAMS_OMITTED = " [further parameters omitted]";
protected volatile Cluster cluster;
private volatile ProtocolVersion protocolVersion;
protected volatile int maxQueryStringLength;
protected volatile int maxParameterValueLength;
protected volatile int maxLoggedParameters;
/**
* Private constructor. Instances of QueryLogger should be obtained via the {@link #builder()}
* method.
*/
private QueryLogger(
int maxQueryStringLength, int maxParameterValueLength, int maxLoggedParameters) {
this.maxQueryStringLength = maxQueryStringLength;
this.maxParameterValueLength = maxParameterValueLength;
this.maxLoggedParameters = maxLoggedParameters;
}
/**
* Creates a new {@link QueryLogger.Builder} instance.
*
*
This is a convenience method for {@code new QueryLogger.Builder()}.
*
* @return the new QueryLogger builder.
* @throws NullPointerException if {@code cluster} is {@code null}.
*/
public static QueryLogger.Builder builder() {
return new QueryLogger.Builder();
}
@Override
public void onRegister(Cluster cluster) {
this.cluster = cluster;
}
@Override
public void onUnregister(Cluster cluster) {
// nothing to do
}
/**
* A QueryLogger that uses a constant threshold in milliseconds to track slow queries. This
* implementation is the default and should be preferred to {@link DynamicThresholdQueryLogger}
* which is still in beta state.
*/
public static class ConstantThresholdQueryLogger extends QueryLogger {
private volatile long slowQueryLatencyThresholdMillis;
private ConstantThresholdQueryLogger(
int maxQueryStringLength,
int maxParameterValueLength,
int maxLoggedParameters,
long slowQueryLatencyThresholdMillis) {
super(maxQueryStringLength, maxParameterValueLength, maxLoggedParameters);
this.setSlowQueryLatencyThresholdMillis(slowQueryLatencyThresholdMillis);
}
/**
* Return the threshold in milliseconds beyond which queries are considered 'slow' and logged as
* such by the driver. The default value is {@link #DEFAULT_SLOW_QUERY_THRESHOLD_MS}.
*
* @return The threshold in milliseconds beyond which queries are considered 'slow' and logged
* as such by the driver.
*/
public long getSlowQueryLatencyThresholdMillis() {
return slowQueryLatencyThresholdMillis;
}
/**
* Set the threshold in milliseconds beyond which queries are considered 'slow' and logged as
* such by the driver.
*
* @param slowQueryLatencyThresholdMillis Slow queries threshold in milliseconds. It must be
* strictly positive.
* @throws IllegalArgumentException if {@code slowQueryLatencyThresholdMillis <= 0}.
*/
public void setSlowQueryLatencyThresholdMillis(long slowQueryLatencyThresholdMillis) {
if (slowQueryLatencyThresholdMillis <= 0)
throw new IllegalArgumentException(
"Invalid slowQueryLatencyThresholdMillis, should be > 0, got "
+ slowQueryLatencyThresholdMillis);
this.slowQueryLatencyThresholdMillis = slowQueryLatencyThresholdMillis;
}
@Override
protected void maybeLogNormalOrSlowQuery(Host host, Statement statement, long latencyMs) {
if (latencyMs > slowQueryLatencyThresholdMillis) {
maybeLogSlowQuery(host, statement, latencyMs);
} else {
maybeLogNormalQuery(host, statement, latencyMs);
}
}
protected void maybeLogSlowQuery(Host host, Statement statement, long latencyMs) {
if (SLOW_LOGGER.isDebugEnabled()) {
String message =
String.format(
SLOW_TEMPLATE_MILLIS,
cluster.getClusterName(),
host,
latencyMs,
statementAsString(statement));
logQuery(statement, null, SLOW_LOGGER, message);
}
}
}
/**
* A QueryLogger that uses a dynamic threshold in milliseconds to track slow queries.
*
*
Dynamic thresholds are based on per-host latency percentiles, as computed by {@link
* PercentileTracker}.
*/
public static class DynamicThresholdQueryLogger extends QueryLogger {
private volatile double slowQueryLatencyThresholdPercentile;
private volatile PercentileTracker percentileLatencyTracker;
private DynamicThresholdQueryLogger(
int maxQueryStringLength,
int maxParameterValueLength,
int maxLoggedParameters,
double slowQueryLatencyThresholdPercentile,
PercentileTracker percentileLatencyTracker) {
super(maxQueryStringLength, maxParameterValueLength, maxLoggedParameters);
this.setSlowQueryLatencyThresholdPercentile(slowQueryLatencyThresholdPercentile);
this.setPercentileLatencyTracker(percentileLatencyTracker);
}
/**
* Return the percentile tracker to use for recording per-host latency histograms. Cannot be
* {@code null}.
*
* @return the percentile tracker to use.
*/
public PercentileTracker getPercentileLatencyTracker() {
return percentileLatencyTracker;
}
/**
* Set the percentile tracker to use for recording per-host latency histograms. Cannot be {@code
* null}.
*
* @param percentileLatencyTracker the percentile tracker instance to use.
* @throws IllegalArgumentException if {@code percentileLatencyTracker == null}.
*/
public void setPercentileLatencyTracker(PercentileTracker percentileLatencyTracker) {
if (percentileLatencyTracker == null)
throw new IllegalArgumentException("perHostPercentileLatencyTracker cannot be null");
this.percentileLatencyTracker = percentileLatencyTracker;
}
/**
* Return the threshold percentile beyond which queries are considered 'slow' and logged as such
* by the driver. The default value is {@link #DEFAULT_SLOW_QUERY_THRESHOLD_PERCENTILE}.
*
* @return threshold percentile beyond which queries are considered 'slow' and logged as such by
* the driver.
*/
public double getSlowQueryLatencyThresholdPercentile() {
return slowQueryLatencyThresholdPercentile;
}
/**
* Set the threshold percentile beyond which queries are considered 'slow' and logged as such by
* the driver.
*
* @param slowQueryLatencyThresholdPercentile Slow queries threshold percentile. It must be
* comprised between 0 inclusive and 100 exclusive.
* @throws IllegalArgumentException if {@code slowQueryLatencyThresholdPercentile < 0 ||
* slowQueryLatencyThresholdPercentile >= 100}.
*/
public void setSlowQueryLatencyThresholdPercentile(double slowQueryLatencyThresholdPercentile) {
if (slowQueryLatencyThresholdPercentile < 0.0 || slowQueryLatencyThresholdPercentile >= 100.0)
throw new IllegalArgumentException(
"Invalid slowQueryLatencyThresholdPercentile, should be >= 0 and < 100, got "
+ slowQueryLatencyThresholdPercentile);
this.slowQueryLatencyThresholdPercentile = slowQueryLatencyThresholdPercentile;
}
@Override
protected void maybeLogNormalOrSlowQuery(Host host, Statement statement, long latencyMs) {
long threshold =
percentileLatencyTracker.getLatencyAtPercentile(
host, statement, null, slowQueryLatencyThresholdPercentile);
if (threshold >= 0 && latencyMs > threshold) {
maybeLogSlowQuery(host, statement, latencyMs, threshold);
} else {
maybeLogNormalQuery(host, statement, latencyMs);
}
}
protected void maybeLogSlowQuery(
Host host, Statement statement, long latencyMs, long threshold) {
if (SLOW_LOGGER.isDebugEnabled()) {
String message =
String.format(
SLOW_TEMPLATE_PERCENTILE,
cluster.getClusterName(),
host,
latencyMs,
slowQueryLatencyThresholdPercentile,
threshold,
statementAsString(statement));
logQuery(statement, null, SLOW_LOGGER, message);
}
}
@Override
public void onRegister(Cluster cluster) {
super.onRegister(cluster);
cluster.register(percentileLatencyTracker);
}
// Don't unregister the latency tracker in onUnregister, we can't guess if it's being used by
// another component
// or not.
}
/** Helper class to build {@link QueryLogger} instances with a fluent API. */
public static class Builder {
private int maxQueryStringLength = DEFAULT_MAX_QUERY_STRING_LENGTH;
private int maxParameterValueLength = DEFAULT_MAX_PARAMETER_VALUE_LENGTH;
private int maxLoggedParameters = DEFAULT_MAX_LOGGED_PARAMETERS;
private long slowQueryLatencyThresholdMillis = DEFAULT_SLOW_QUERY_THRESHOLD_MS;
private double slowQueryLatencyThresholdPercentile = DEFAULT_SLOW_QUERY_THRESHOLD_PERCENTILE;
private PercentileTracker percentileLatencyTracker;
private boolean constantThreshold = true;
/**
* Enables slow query latency tracking based on constant thresholds.
*
*
Note: You should either use {@link #withConstantThreshold(long) constant thresholds} or
* {@link #withDynamicThreshold(PercentileTracker, double) dynamic thresholds}, not both.
*
* @param slowQueryLatencyThresholdMillis The threshold in milliseconds beyond which queries are
* considered 'slow' and logged as such by the driver. The default value is {@link
* #DEFAULT_SLOW_QUERY_THRESHOLD_MS}
* @return this {@link Builder} instance (for method chaining).
*/
public Builder withConstantThreshold(long slowQueryLatencyThresholdMillis) {
this.slowQueryLatencyThresholdMillis = slowQueryLatencyThresholdMillis;
constantThreshold = true;
return this;
}
/**
* Enables slow query latency tracking based on dynamic thresholds.
*
*
Dynamic thresholds are based on latency percentiles, as computed by {@link
* PercentileTracker}.
*
*
Note: You should either use {@link #withConstantThreshold(long) constant thresholds} or
* {@link #withDynamicThreshold(PercentileTracker, double) dynamic thresholds}, not both.
*
* @param percentileLatencyTracker the {@link PercentileTracker} instance to use for recording
* latency histograms. Cannot be {@code null}. It will get {@link
* Cluster#register(LatencyTracker) registered} with the cluster at the same time as this
* logger.
* @param slowQueryLatencyThresholdPercentile Slow queries threshold percentile. It must be
* comprised between 0 inclusive and 100 exclusive. The default value is {@link
* #DEFAULT_SLOW_QUERY_THRESHOLD_PERCENTILE}
* @return this {@link Builder} instance (for method chaining).
*/
public Builder withDynamicThreshold(
PercentileTracker percentileLatencyTracker, double slowQueryLatencyThresholdPercentile) {
this.percentileLatencyTracker = percentileLatencyTracker;
this.slowQueryLatencyThresholdPercentile = slowQueryLatencyThresholdPercentile;
constantThreshold = false;
return this;
}
/**
* Set the maximum length of a CQL query string that can be logged verbatim by the driver. Query
* strings longer than this value will be truncated when logged.
*
* @param maxQueryStringLength The maximum length of a CQL query string that can be logged
* verbatim by the driver. It must be strictly positive or {@code -1}, in which case the
* query is never truncated (use with care). The default value is {@link
* #DEFAULT_MAX_QUERY_STRING_LENGTH}.
* @return this {@link Builder} instance (for method chaining).
*/
public Builder withMaxQueryStringLength(int maxQueryStringLength) {
this.maxQueryStringLength = maxQueryStringLength;
return this;
}
/**
* Set the maximum length of a query parameter value that can be logged verbatim by the driver.
* Parameter values longer than this value will be truncated when logged.
*
* @param maxParameterValueLength The maximum length of a query parameter value that can be
* logged verbatim by the driver. It must be strictly positive or {@code -1}, in which case
* the parameter value is never truncated (use with care). The default value is {@link
* #DEFAULT_MAX_PARAMETER_VALUE_LENGTH}.
* @return this {@link Builder} instance (for method chaining).
*/
public Builder withMaxParameterValueLength(int maxParameterValueLength) {
this.maxParameterValueLength = maxParameterValueLength;
return this;
}
/**
* Set the maximum number of query parameters that can be logged by the driver. Queries with a
* number of parameters higher than this value will not have all their parameters logged.
*
* @param maxLoggedParameters The maximum number of query parameters that can be logged by the
* driver. It must be strictly positive or {@code -1}, in which case all parameters will be
* logged, regardless of their number (use with care). The default value is {@link
* #DEFAULT_MAX_LOGGED_PARAMETERS}.
* @return this {@link Builder} instance (for method chaining).
*/
public Builder withMaxLoggedParameters(int maxLoggedParameters) {
this.maxLoggedParameters = maxLoggedParameters;
return this;
}
/**
* Build the {@link QueryLogger} instance.
*
* @return the {@link QueryLogger} instance.
* @throws IllegalArgumentException if the builder is unable to build a valid instance due to
* incorrect settings.
*/
public QueryLogger build() {
if (constantThreshold) {
return new ConstantThresholdQueryLogger(
maxQueryStringLength,
maxParameterValueLength,
maxLoggedParameters,
slowQueryLatencyThresholdMillis);
} else {
return new DynamicThresholdQueryLogger(
maxQueryStringLength,
maxParameterValueLength,
maxLoggedParameters,
slowQueryLatencyThresholdPercentile,
percentileLatencyTracker);
}
}
}
// Getters and Setters
/**
* Return the maximum length of a CQL query string that can be logged verbatim by the driver.
* Query strings longer than this value will be truncated when logged. The default value is {@link
* #DEFAULT_MAX_QUERY_STRING_LENGTH}.
*
* @return The maximum length of a CQL query string that can be logged verbatim by the driver.
*/
public int getMaxQueryStringLength() {
return maxQueryStringLength;
}
/**
* Set the maximum length of a CQL query string that can be logged verbatim by the driver. Query
* strings longer than this value will be truncated when logged.
*
* @param maxQueryStringLength The maximum length of a CQL query string that can be logged
* verbatim by the driver. It must be strictly positive or {@code -1}, in which case the query
* is never truncated (use with care).
* @throws IllegalArgumentException if {@code maxQueryStringLength <= 0 && maxQueryStringLength !=
* -1}.
*/
public void setMaxQueryStringLength(int maxQueryStringLength) {
if (maxQueryStringLength <= 0 && maxQueryStringLength != -1)
throw new IllegalArgumentException(
"Invalid maxQueryStringLength, should be > 0 or -1, got " + maxQueryStringLength);
this.maxQueryStringLength = maxQueryStringLength;
}
/**
* Return the maximum length of a query parameter value that can be logged verbatim by the driver.
* Parameter values longer than this value will be truncated when logged. The default value is
* {@link #DEFAULT_MAX_PARAMETER_VALUE_LENGTH}.
*
* @return The maximum length of a query parameter value that can be logged verbatim by the
* driver.
*/
public int getMaxParameterValueLength() {
return maxParameterValueLength;
}
/**
* Set the maximum length of a query parameter value that can be logged verbatim by the driver.
* Parameter values longer than this value will be truncated when logged.
*
* @param maxParameterValueLength The maximum length of a query parameter value that can be logged
* verbatim by the driver. It must be strictly positive or {@code -1}, in which case the
* parameter value is never truncated (use with care).
* @throws IllegalArgumentException if {@code maxParameterValueLength <= 0 &&
* maxParameterValueLength != -1}.
*/
public void setMaxParameterValueLength(int maxParameterValueLength) {
if (maxParameterValueLength <= 0 && maxParameterValueLength != -1)
throw new IllegalArgumentException(
"Invalid maxParameterValueLength, should be > 0 or -1, got " + maxParameterValueLength);
this.maxParameterValueLength = maxParameterValueLength;
}
/**
* Return the maximum number of query parameters that can be logged by the driver. Queries with a
* number of parameters higher than this value will not have all their parameters logged. The
* default value is {@link #DEFAULT_MAX_LOGGED_PARAMETERS}.
*
* @return The maximum number of query parameters that can be logged by the driver.
*/
public int getMaxLoggedParameters() {
return maxLoggedParameters;
}
/**
* Set the maximum number of query parameters that can be logged by the driver. Queries with a
* number of parameters higher than this value will not have all their parameters logged.
*
* @param maxLoggedParameters the maximum number of query parameters that can be logged by the
* driver. It must be strictly positive or {@code -1}, in which case all parameters will be
* logged, regardless of their number (use with care).
* @throws IllegalArgumentException if {@code maxLoggedParameters <= 0 && maxLoggedParameters !=
* -1}.
*/
public void setMaxLoggedParameters(int maxLoggedParameters) {
if (maxLoggedParameters <= 0 && maxLoggedParameters != -1)
throw new IllegalArgumentException(
"Invalid maxLoggedParameters, should be > 0 or -1, got " + maxLoggedParameters);
this.maxLoggedParameters = maxLoggedParameters;
}
/** {@inheritDoc} */
@Override
public void update(Host host, Statement statement, Exception exception, long newLatencyNanos) {
if (cluster == null)
throw new IllegalStateException(
"This method should only be called after the logger has been registered with a cluster");
if (statement instanceof StatementWrapper)
statement = ((StatementWrapper) statement).getWrappedStatement();
long latencyMs = NANOSECONDS.toMillis(newLatencyNanos);
if (exception == null) {
maybeLogNormalOrSlowQuery(host, statement, latencyMs);
} else {
maybeLogErrorQuery(host, statement, exception, latencyMs);
}
}
protected abstract void maybeLogNormalOrSlowQuery(Host host, Statement statement, long latencyMs);
protected void maybeLogNormalQuery(Host host, Statement statement, long latencyMs) {
if (NORMAL_LOGGER.isDebugEnabled()) {
String message =
String.format(
NORMAL_TEMPLATE,
cluster.getClusterName(),
host,
latencyMs,
statementAsString(statement));
logQuery(statement, null, NORMAL_LOGGER, message);
}
}
protected void maybeLogErrorQuery(
Host host, Statement statement, Exception exception, long latencyMs) {
if (ERROR_LOGGER.isDebugEnabled()
&& !(exception instanceof CancelledSpeculativeExecutionException)) {
String message =
String.format(
ERROR_TEMPLATE,
cluster.getClusterName(),
host,
latencyMs,
statementAsString(statement));
logQuery(statement, exception, ERROR_LOGGER, message);
}
}
protected void logQuery(Statement statement, Exception exception, Logger logger, String message) {
boolean showParameterValues = logger.isTraceEnabled();
if (showParameterValues) {
StringBuilder params = new StringBuilder();
if (statement instanceof BoundStatement) {
appendParameters((BoundStatement) statement, params, maxLoggedParameters);
} else if (statement instanceof SimpleStatement) {
appendParameters((SimpleStatement) statement, params, maxLoggedParameters);
} else if (statement instanceof BatchStatement) {
BatchStatement batchStatement = (BatchStatement) statement;
int remaining = maxLoggedParameters;
for (Statement inner : batchStatement.getStatements()) {
if (inner instanceof BoundStatement) {
remaining = appendParameters((BoundStatement) inner, params, remaining);
} else if (inner instanceof SimpleStatement) {
remaining = appendParameters((SimpleStatement) inner, params, remaining);
}
}
} else if (statement instanceof BuiltStatement) {
appendParameters((BuiltStatement) statement, params, maxLoggedParameters);
}
if (params.length() > 0) params.append("]");
logger.trace(message + params, exception);
} else {
logger.debug(message, exception);
}
}
protected String statementAsString(Statement statement) {
StringBuilder sb = new StringBuilder();
if (statement instanceof BatchStatement) {
BatchStatement bs = (BatchStatement) statement;
int statements = bs.getStatements().size();
int boundValues = countBoundValues(bs);
sb.append("[" + statements + " statements, " + boundValues + " bound values] ");
} else if (statement instanceof BoundStatement) {
int boundValues = ((BoundStatement) statement).wrapper.values.length;
sb.append("[" + boundValues + " bound values] ");
} else if (statement instanceof SimpleStatement) {
int boundValues = ((SimpleStatement) statement).valuesCount();
sb.append("[" + boundValues + " bound values] ");
}
append(statement, sb, maxQueryStringLength);
return sb.toString();
}
protected int countBoundValues(BatchStatement bs) {
int count = 0;
for (Statement s : bs.getStatements()) {
if (s instanceof BoundStatement) count += ((BoundStatement) s).wrapper.values.length;
else if (s instanceof SimpleStatement) count += ((SimpleStatement) s).valuesCount();
}
return count;
}
protected int appendParameters(BoundStatement statement, StringBuilder buffer, int remaining) {
if (remaining == 0) return 0;
ColumnDefinitions metadata = statement.preparedStatement().getVariables();
int numberOfParameters = metadata.size();
if (numberOfParameters > 0) {
List definitions = metadata.asList();
int numberOfLoggedParameters;
if (remaining == -1) {
numberOfLoggedParameters = numberOfParameters;
} else {
numberOfLoggedParameters = Math.min(remaining, numberOfParameters);
remaining -= numberOfLoggedParameters;
}
for (int i = 0; i < numberOfLoggedParameters; i++) {
if (buffer.length() == 0) buffer.append(" [");
else buffer.append(", ");
String value =
statement.isSet(i)
? parameterValueAsString(definitions.get(i), statement.wrapper.values[i])
: "";
buffer.append(String.format("%s:%s", metadata.getName(i), value));
}
if (numberOfLoggedParameters < numberOfParameters) {
buffer.append(FURTHER_PARAMS_OMITTED);
}
}
return remaining;
}
protected String parameterValueAsString(ColumnDefinitions.Definition definition, ByteBuffer raw) {
String valueStr;
if (raw == null || raw.remaining() == 0) {
valueStr = "NULL";
} else {
DataType type = definition.getType();
CodecRegistry codecRegistry = cluster.getConfiguration().getCodecRegistry();
TypeCodec