public void log(QueryLogParams params) {
    _logger.debug("Broker Response: {}", params._response);

    if (!(_logRateLimiter.tryAcquire() || shouldForceLog(params))) {
      _numDroppedLogs.incrementAndGet();
      return;
    }

    final StringBuilder queryLogBuilder = new StringBuilder();
    for (QueryLogEntry value : QUERY_LOG_ENTRY_VALUES) {
      value.format(queryLogBuilder, this, params);
      queryLogBuilder.append(',');
    }

    // always log the query last - don't add this to the QueryLogEntry enum
    queryLogBuilder.append("query=")
        .append(StringUtils.substring(params._requestContext.getQuery(), 0, _maxQueryLengthToLog));
    _logger.info(queryLogBuilder.toString());

    if (_droppedLogRateLimiter.tryAcquire()) {
      // use getAndSet to 0 so that there will be no race condition between
      // loggers that increment this counter and this thread
      long numDroppedLogsSinceLastLog = _numDroppedLogs.getAndSet(0);
      if (numDroppedLogsSinceLastLog > 0) {
        _logger.warn("{} logs were dropped. (log max rate per second: {})", numDroppedLogsSinceLastLog,
            _logRateLimiter.getRate());
      }
    }
  }
@Test(timeOut = 10_000L)
  public void shouldHandleRaceConditionsWithDroppedQueries()
      throws InterruptedException {
    // Given:
    final CountDownLatch logLatch = new CountDownLatch(1);
    Mockito.when(_logRateLimiter.tryAcquire()).thenReturn(false)
        .thenReturn(true)   // this one will block when it hits tryAcquire()
        .thenReturn(false)  // this one just increments the dropped logs
        .thenAnswer(invocation -> {
          // this one will unblock the tryAcquire, but only after
          // the first thread has reached _droppedRateLimiter#tryAcquire()
          logLatch.await();
          return true;
        });

    // ensure that the tryAcquire only succeeds after three other
    // logs have went through (see logAndDecrement)
    final CountDownLatch dropLogLatch = new CountDownLatch(3);
    Mockito.when(_droppedRateLimiter.tryAcquire()).thenAnswer(invocation -> {
      logLatch.countDown();
      dropLogLatch.await();
      return true;
    }).thenReturn(true);

    QueryLogger.QueryLogParams params = generateParams(false, 0, 456);
    QueryLogger queryLogger = new QueryLogger(_logRateLimiter, 100, true, _logger, _droppedRateLimiter);

    ExecutorService executorService = Executors.newFixedThreadPool(4);

    // When:
    try {
      // use logAndDecrement on all invocations since any one of them could
      // be the one that blocks
      Runnable logAndDecrement = () -> {
        queryLogger.log(params);
        dropLogLatch.countDown();
      };

      executorService.submit(logAndDecrement); // 1 this one gets dropped
      executorService.submit(logAndDecrement); // 2 this one succeeds, but blocks
      executorService.submit(logAndDecrement); // 3 this one gets dropped
      executorService.submit(logAndDecrement); // 4 this one succeeds, and unblocks (2)
    } finally {
      executorService.shutdown();
      Assert.assertTrue(executorService.awaitTermination(10, TimeUnit.SECONDS), "expected shutdown to complete");
    }

    // Then:
    Assert.assertEquals(_numDropped.size(), 1); // the second successful one never logs to warn
    Assert.assertEquals((long) _numDropped.get(0), 2L);
  }