void checkText()

in languagetool-server/src/main/java/org/languagetool/server/TextChecker.java [353:828]


  void checkText(AnnotatedText aText, HttpExchange httpExchange, Map<String, String> params, ErrorRequestLimiter errorRequestLimiter,
                 String remoteAddress) throws Exception {
    checkParams(params);
    long timeStart = System.currentTimeMillis();
    String authHeader = ServerTools.getAuthHeader(httpExchange.getRequestHeaders());
    UserLimits limits = ServerTools.getUserLimits(params, config, authHeader);

    if (Premium.isPremiumStatusCheck(aText)) {
      Language premiumStatusCheckLang = Languages.getLanguageForShortCode("en-US");
      List<RuleMatch> matches = new ArrayList<>();
      if (limits.hasPremium() || config.isPremiumAlways()) {
        matches.add(new RuleMatch(new Rule() {
          @Override
          public String getId() {
            return "PREMIUM_FAKE_RULE";
          }

          @Override
          public String getDescription() {
            return "PREMIUM_FAKE_RULE";
          }

          @Override
          public RuleMatch[] match(AnalyzedSentence sentence) throws IOException {
            return RuleMatch.EMPTY_ARRAY;
          }
        }, null,0,1,""));
      }
      DetectedLanguage detectedLanguage = new DetectedLanguage(premiumStatusCheckLang, premiumStatusCheckLang, 0.99999076F, "ngram");
      int compactMode = Integer.parseInt(params.getOrDefault("c", "0"));
      String response = getResponse(aText, premiumStatusCheckLang, detectedLanguage, premiumStatusCheckLang, Collections.singletonList(new CheckResults(matches, Collections.emptyList())), Collections.emptyList(), null, compactMode,
        !limits.hasPremium(), JLanguageTool.Mode.ALL);
      setHeaders(httpExchange);
      httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.getBytes(ENCODING).length);
      httpExchange.getResponseBody().write(response.getBytes(ENCODING));
      return;
    }

    String requestId = httpExchange.getRequestHeaders().getFirst("X-Request-ID");

    // logging information
    String agent = params.get("useragent") != null ? params.get("useragent") : "-";
    Long agentId = null, userId = null;
    if (databaseLogger.isLogging()) {
      DatabaseAccess db = DatabaseAccess.getInstance();
      agentId = db.getOrCreateClientId(params.get("useragent"));
      userId = limits.getPremiumUid();
    }
    String referrer = httpExchange.getRequestHeaders().getFirst("Referer");
    String userAgent = httpExchange.getRequestHeaders().getFirst("User-Agent");

    if (!config.isAnonymousAccessAllowed() && limits.getPremiumUid() == null) {
      throw new AuthException("Anonymous access is prohibited on this server, please provide authentication.");
    }

    int length = aText.getPlainText().length();
    if ("true".equals(params.get("languageChanged"))) {
      log.info("languageChanged to " + params.get("language") + " for text with length " + aText.getPlainText().trim().length());
    }
    if (length > limits.getMaxTextLength()) {
      ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.MAX_TEXT_SIZE);
      throw new TextTooLongException("Your text exceeds the limit of " + limits.getMaxTextLength() +
              " characters (it's " + length + " characters). Please submit a shorter text.");
    }
    // static because we can't rely on errorRequestLimiter, null when timeoutRequestLimit option not set
    if (!config.isLocalApiMode()) {
      try {
        RequestLimiter.checkUserLimit(referrer, userAgent, limits);
      } catch (TooManyRequestsException e) {
        String response = "Error: Access denied: " + e.getMessage();
        httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_FORBIDDEN, response.getBytes(ENCODING).length);
        httpExchange.getResponseBody().write(response.getBytes(ENCODING));
        String message = "Blocked request from uid:" + userId + " because user limit is reached: ";
        message += "limit = " + limits.getRequestsPerDay() + ", mode = " + limits.getLimitEnforcementMode() + ". ";
        message += "Access from " + remoteAddress + ", ";
        message += "HTTP user agent: " + userAgent + ", ";
        message += "User agent param: " + params.get("useragent") + ", ";
        message += "Referrer: " + referrer + ", ";
        message += "language: " + params.get("language") + ", ";
        message += "h: " + reqCounter.getHandleCount() + ", ";
        message += "r: " + reqCounter.getRequestCount();
        if (params.get("username") != null) {
          message += ", user: " + params.get("username");
        }
        if (params.get("apiKey") != null) {
          message += ", apiKey: " + params.get("apiKey");
        }
        String text = params.get("text");
        if (text != null) {
          message += ", text length: " + text.length();
        }
        log.warn(message);
        return;
      }
    }
    List<String> dictGroups = null;
    String dictName = "default";
    if (params.containsKey("dicts")) {
      dictGroups = Arrays.asList(params.get("dicts").split(","));
      dictGroups.sort(Comparator.naturalOrder());
      dictName = "groups_" + String.join(",", dictGroups);
    }
    final List<String> finalDictGroups = dictGroups;
    List<String> dictWords = limits.getPremiumUid() != null ?
      TelemetryProvider.INSTANCE.createSpan(SPAN_NAME_PREFIX +"GetUserDictWords", Attributes.empty(), () -> getUserDictWords(limits, finalDictGroups)) : Collections.emptyList();

    boolean filterDictionaryMatches = "true".equals(params.getOrDefault("filterDictionaryMatches", "true"));

    Long textSessionId = computeTextSessionID(params.get("textSessionId"), remoteAddress);

    List<String> abTest = AB_TEST_SERVICE.getActiveAbTestForClient(params, config);

    boolean enableHiddenRules = "true".equals(params.get("enableHiddenRules"));
    if (limits.hasPremium()) {
      enableHiddenRules = false;
    }

    boolean autoDetectLanguage = getLanguageAutoDetect(params);
    List<String> preferredVariants = getPreferredVariants(params);
    if (params.get("noopLanguages") != null && !autoDetectLanguage) {
      ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.INVALID_REQUEST);
      throw new BadRequestException("You can specify 'noopLanguages' only when also using 'language=auto'");
    }
    List<String> noopLangs = params.get("noopLanguages") != null ?
            Arrays.asList(params.get("noopLanguages").split(",")) : Collections.emptyList();
    List<String> preferredLangs = params.get("preferredLanguages") != null ?
            Arrays.asList(params.get("preferredLanguages").split(",")) : Collections.emptyList();
    DetectedLanguage detLang = TelemetryProvider.INSTANCE.createSpan(SPAN_NAME_PREFIX + "DetetectLanguage", Attributes.empty(), () -> getLanguage(aText.getPlainText(), params, preferredVariants, noopLangs, preferredLangs,
      params.getOrDefault("ld", "control").equalsIgnoreCase("test")));
    Language lang = detLang.getGivenLanguage();

    List<Rule> userRules = TelemetryProvider.INSTANCE.createSpan(SPAN_NAME_PREFIX + "GetUserRules", Attributes.empty(), () -> getUserRules(limits, lang, finalDictGroups));
    String ltAgent = params.getOrDefault("useragent", "unknown");
    Pattern trustedSourcesPattern = config.getTrustedSources();
    boolean trustedSource = trustedSourcesPattern == null || (limits.hasPremium() || trustedSourcesPattern.matcher(ltAgent).matches());
    boolean optInThirdPartyAI = isOptInThirdPartyAI(limits, params, config);

    // set default value for tokenType
    UserConfig.TokenType tokenType = authHeader == null ? UserConfig.TokenType.NO_TOKEN : UserConfig.TokenType.INVALID_TOKEN;
    JwtContent jwtContent = limits.getJwtContent();
    if (jwtContent != null && jwtContent.isValid()) {
      tokenType = jwtContent.isPremium() ? UserConfig.TokenType.TRIAL_TOKEN : UserConfig.TokenType.TEST_TOKEN;
    }
    UserConfig userConfig =
      new UserConfig(dictWords, userRules,
                     getRuleValues(params), config.getMaxSpellingSuggestions(),
                     limits.getPremiumUid(), dictName, limits.getDictCacheSize(),
                     null, filterDictionaryMatches, abTest, textSessionId,
                     !limits.hasPremium() && enableHiddenRules, preferredLangs, trustedSource, optInThirdPartyAI, limits.hasPremium(), tokenType);

    //print("Check start: " + text.length() + " chars, " + langParam);

    // == temporary counting code ======================================
    /*
    if (httpExchange.getRequestHeaders() != null && httpExchange.getRequestHeaders().get("Accept-Language") != null) {
      List<String> langs = httpExchange.getRequestHeaders().get("Accept-Language");
      if (langs.size() > 0) {
        String[] split = langs.get(0).split(",");
        if (split.length > 0 && detLang.getDetectedLanguage() != null && detLang.getDetectedLanguage().getShortCode().equals("en")) {
          int theCount1 = StringUtils.countMatches(aText.toString(), " the ");
          int theCount2 = StringUtils.countMatches(aText.toString(), "The ");
          String browserLang = split[0];
          System.out.println("STAT\t" + detLang.getDetectedLanguage().getShortCode() + "\t" + detLang.getDetectionConfidence() + "\t" + aText.toString().length() + "\t" + browserLang + "\t" + theCount1 + "\t" + theCount2);
        }
      }
    }
    */
    // ========================================

    Integer count = languageCheckCounts.get(lang.getShortCodeWithCountryAndVariant());
    if (count == null) {
      count = 1;
    } else {
      count++;
    }
    //print("Starting check: " + aText.getPlainText().length() + " chars, #" + count);
    String motherTongueParam = params.get("motherTongue");
    Language motherTongue = motherTongueParam != null ? parseLanguage(motherTongueParam) : null;
    boolean useEnabledOnly = "yes".equals(params.get("enabledOnly")) || "true".equals(params.get("enabledOnly"));
    List<Language> altLanguages = new ArrayList<>();
    if (params.get("altLanguages") != null) {
      String[] altLangParams = COMMA_WHITESPACE_PATTERN.split(params.get("altLanguages"));
      for (String langCode : altLangParams) {
        Language altLang = parseLanguage(langCode);
        altLanguages.add(altLang);
        if (altLang.hasVariant() && !altLang.isVariant()) {
          ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.INVALID_REQUEST);
          throw new BadRequestException("You specified altLanguage '" + langCode + "', but for this language you need to specify a variant, e.g. 'en-GB' instead of just 'en'");
        }
      }
    }
    List<String> enabledRules = getEnabledRuleIds(params);
    List<String> disabledRules = getDisabledRuleIds(params);
    List<CategoryId> enabledCategories = getCategoryIds("enabledCategories", params);
    List<CategoryId> disabledCategories = getCategoryIds("disabledCategories", params);


    if (shouldRunRestrictedRulesTest(params, agent, lang, abTest)) {
      log.info("Running test with restricted rules for user: {}, language: {}, client: {}",
        params.getOrDefault("username", ""), lang.getShortCodeWithCountryAndVariant(), agent);
      useEnabledOnly = true;
      enabledRules = onlyTestRules;
      // need to clear these settings, leads to conflict with useEnabledOnly otherwise
      disabledRules = Collections.emptyList();
      disabledCategories = Collections.emptyList();
    }


    if ((disabledRules.size() > 0 || disabledCategories.size() > 0) && useEnabledOnly) {
      ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.INVALID_REQUEST);
      throw new BadRequestException("You cannot specify disabled rules or categories using enabledOnly=true");
    }
    if (enabledRules.isEmpty() && enabledCategories.isEmpty() && useEnabledOnly) {
      ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.INVALID_REQUEST);
      throw new BadRequestException("You must specify enabled rules or categories when using enabledOnly=true");
    }

    boolean enableTempOffRules = "true".equals(params.get("enableTempOffRules"));
    boolean useQuerySettings = enabledRules.size() > 0 || disabledRules.size() > 0 ||
            enabledCategories.size() > 0 || disabledCategories.size() > 0 || enableTempOffRules;
    boolean allowIncompleteResults = "true".equals(params.get("allowIncompleteResults"));
    JLanguageTool.Mode mode = ServerTools.getMode(params);
    JLanguageTool.Level level = ServerTools.getLevel(params);
    String[] toneTagNames = params.get("toneTags") != null ? params.get("toneTags").split(",") : null;
    Set<ToneTag> toneTags = new HashSet<>(ToneTag.values().length);
    if (toneTagNames != null) {
      if (toneTagNames.length == 1 && toneTagNames[0].isEmpty()) { //&toneTags=
        toneTags.add(ToneTag.ALL_WITHOUT_GOAL_SPECIFIC);
      } else {
        for (String toneTagName : toneTagNames) {
          if (toneTagNames.length > 1 && (toneTagName.equals("NO_TONE_RULE") || toneTagName.equals("ALL_TONE_RULES"))) { //&toneTags=ALL_TONE_RULES or //&toneTags=NO_TONE_RULE
            log.warn("NO_TONE_RULE and ALL_TONE_RULES will be ignored if more than one toneTag is in params.");
            continue;
          }
          try {
            toneTags.add(ToneTag.valueOf(toneTagName));
          } catch (IllegalArgumentException ex) {
            //just ignore unsupported toneTags
            log.warn("Unsupported toneTag found in params: {}", toneTagName);
          }
        }
      }
    } else {
      toneTags.add(ToneTag.ALL_WITHOUT_GOAL_SPECIFIC); //No toneTags param in request
    }

    String rIp = LanguageToolHttpHandler.getRealRemoteAddressOrNull(httpExchange,config);
    if (rIp != null && mode.equals(JLanguageTool.Mode.ALL_BUT_TEXTLEVEL_ONLY)) {
      log.info("L-IP{}:{}", lang.getShortCodeWithCountryAndVariant(), rIp);
    }

    String callback = params.get("callback");
    // allowed to log input on errors?
    boolean inputLogging = !params.getOrDefault("inputLogging", "").equals("no");
    boolean premiumStatus = limits.hasPremium();
    QueryParams qParams = new QueryParams(altLanguages, enabledRules, disabledRules,
      enabledCategories, disabledCategories, useEnabledOnly,
      useQuerySettings, allowIncompleteResults, enableHiddenRules, premiumStatus, enableTempOffRules, mode, level, toneTags, callback, inputLogging);

    int textSize = length;
    List<CheckResults> ruleMatchesSoFar = Collections.synchronizedList(new ArrayList<>());
    Future<List<CheckResults>> future;
    try {
      future = executorService.submit(() -> {
        try (MDC.MDCCloseable c = MDC.putCloseable("rID", LanguageToolHttpHandler.getRequestId(httpExchange))) {
          log.debug("Starting text check on {} chars; params: {}", length, qParams);
          long time = System.currentTimeMillis();
          List<CheckResults> results = getRuleMatches(aText, lang, motherTongue, params, qParams, userConfig, f -> ruleMatchesSoFar.add(new CheckResults(Collections.singletonList(f), Collections.emptyList())));
          log.debug("Finished text check in {}ms. Starting suggestion generation.", System.currentTimeMillis() - time);
          time = System.currentTimeMillis();
          // generate suggestions, otherwise this is not part of the timeout logic and not properly measured in the metrics
          results.stream().flatMap(r -> r.getRuleMatches().stream()).forEach(RuleMatch::computeLazySuggestedReplacements);
          log.debug("Finished suggestion generation in {}ms, returning results.", System.currentTimeMillis() - time);
          return results;
        }
      });
    } catch (RejectedExecutionException e) {
      throw new UnavailableException("Server overloaded, please try again later", e);
    }
    String incompleteResultReason = null;
    List<CheckResults> res;
    Attributes textCheckingAttributes = Attributes.builder()
            .put("text.language", lang.getShortCode())
            .put("text.size", textSize)
            .put("userRules.size", userRules.size())
            .put("dictionary.size", dictWords.size())
            .build();
    Integer finalCount = count;
    Map.Entry<List<CheckResults>, String> resAndReason = TelemetryProvider.INSTANCE.createSpan(SPAN_NAME_PREFIX + "GetRuleMatches", textCheckingAttributes, (span) -> {
        List<CheckResults> localRes;
        String localReason = null;
        try {
          if (limits.getMaxCheckTimeMillis() < 0) {
            localRes = future.get();
          } else {
            localRes = future.get(limits.getMaxCheckTimeMillis(), TimeUnit.MILLISECONDS);
          }
        } catch (ExecutionException e) {
          future.cancel(true);
          if (ExceptionUtils.getRootCause(e) instanceof ErrorRateTooHighException) {
            ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.TOO_MANY_ERRORS);
          }
          if (qParams.allowIncompleteResults && ExceptionUtils.getRootCause(e) instanceof ErrorRateTooHighException) {
            log.warn(e.getMessage() + " - returning " + ruleMatchesSoFar.size() + " matches found so far. " +
              "Detected language: " + detLang + ", " + ServerTools.getLoggingInfo(remoteAddress, null, -1, httpExchange,
              params, System.currentTimeMillis() - timeStart, reqCounter));
            localRes = new ArrayList<>(ruleMatchesSoFar);  // threads might still be running, so make a copy
            localReason = "Results are incomplete: " + ExceptionUtils.getRootCause(e).getMessage();
          } else if (e.getCause() != null && e.getCause() instanceof OutOfMemoryError) {
            throw (OutOfMemoryError) e.getCause();
          } else {
            throw new RuntimeException(ServerTools.cleanUserTextFromMessage(e.getMessage(), params) + ", detected: " + detLang, e);
          }
        } catch (TimeoutException e) {
          boolean cancelled = future.cancel(true);
          Path loadFile = Paths.get("/proc/loadavg");  // works in Linux only(?)
          String loadInfo = loadFile.toFile().exists() ? Files.readAllLines(loadFile).toString() : "(unknown)";
          if (errorRequestLimiter != null) {
            errorRequestLimiter.logAccess(remoteAddress, httpExchange.getRequestHeaders(), params);
          }
          String message = "Text checking took longer than allowed maximum of " + limits.getMaxCheckTimeMillis() +
            " milliseconds (cancelled: " + cancelled +
            ", lang: " + lang.getShortCodeWithCountryAndVariant() +
            ", detected: " + detLang +
            ", #" + finalCount +
            ", " + length + " characters of text" +
            ", mode: " + mode.toString().toLowerCase() +
            ", h: " + reqCounter.getHandleCount() +
            ", r: " + reqCounter.getRequestCount() +
            ", requestId: " + requestId +
            ", system load: " + loadInfo + ")";
          if (qParams.allowIncompleteResults) {
            log.info(message + " - returning " + ruleMatchesSoFar.size() + " matches found so far");
            localRes = new ArrayList<>(ruleMatchesSoFar);  // threads might still be running, so make a copy
            localReason = "Results are incomplete: text checking took longer than allowed maximum of " +
              String.format(Locale.ENGLISH, "%.2f", limits.getMaxCheckTimeMillis() / 1000.0) + " seconds";
            span.setAttribute("incompleteResults", true);
          } else {
            ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.MAX_CHECK_TIME);
            throw new RuntimeException(message, e);
          }
        }
        return new AbstractMap.SimpleEntry(localRes, localReason);
      });
    res = resAndReason.getKey();
    incompleteResultReason = resAndReason.getValue();

    // no lazy computation at later points (outside of timeout enforcement)
    // e.g. ruleMatchesSoFar can have matches without computeLazySuggestedReplacements called yet
    res.forEach(checkResults -> checkResults.getRuleMatches().forEach(RuleMatch::discardLazySuggestedReplacements));

    setHeaders(httpExchange);

    List<RuleMatch> hiddenMatches = new ArrayList<>();
    boolean temporaryPremiumDisabledRuleMatch = false;
    Set<String> temporaryPremiumDisabledRuleMatchedIds = new HashSet<>();
    // filter computed premium matches, convert to hidden matches - no separate hidden matches server needed
    if (!qParams.premium && qParams.enableHiddenRules) {
      List<RuleMatch> allMatches = new ArrayList<>(); // for filtering out overlapping matches, collect across CheckResults
      List<RuleMatch> premiumMatches = new ArrayList<>();
      for (CheckResults result : res) {
        List<RuleMatch> filteredMatches = new ArrayList<>();
        for (RuleMatch match : result.getRuleMatches()) {
          if (Premium.get().isPremiumRule(match.getRule()) && !Premium.isTempNotPremium(match.getRule())) {
            premiumMatches.add(match);
          } else if (userConfig.getAbTest() != null && userConfig.getAbTest().equals("ALLOW_PREMIUM_IN_BASIC") && Premium.get().isPremiumRule(match.getRule()) && Premium.isTempNotPremium(match.getRule())) {
            System.out.println("Rule: " + match.getRule().getId() + " is premium but temporary available in basic");
            filteredMatches.add(match);
            allMatches.add(match);
            temporaryPremiumDisabledRuleMatch = true;
            temporaryPremiumDisabledRuleMatchedIds.add(match.getRule().getId());
          } else {
            // filter out premium matches
            filteredMatches.add(match);
            // keep track for filtering out overlapping matches
            allMatches.add(match);
          }
          // need to replace list, can't iterate and remove since some rules may return unmodifiable lists
          result.setRuleMatches(filteredMatches);
        }
      }
      hiddenMatches.addAll(ResultExtender.getAsHiddenMatches(allMatches, premiumMatches));
    }

    int compactMode = Integer.parseInt(params.getOrDefault("c", "0"));
    String response = getResponse(aText, lang, detLang, motherTongue, res, hiddenMatches, incompleteResultReason, compactMode,
      limits.getPremiumUid() == null, qParams.mode);
    if (qParams.callback != null) {
      // JSONP - still needed today for the special case of hosting your own on-premise LT without SSL
      // and using it from a local MS Word (not Online Word) - issue #89 in the add-in repo:
      response = qParams.callback + "(" + response + ");";
    }
    String messageSent = "sent";
    String languageMessage = lang.getShortCodeWithCountryAndVariant();
    try {
      httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.getBytes(ENCODING).length);
      httpExchange.getResponseBody().write(response.getBytes(ENCODING));
      ServerMetricsCollector.getInstance().logResponse(HttpURLConnection.HTTP_OK);
    } catch (IOException exception) {
      // the client is disconnected
      messageSent = "notSent: " + exception.getMessage();
    }
    if (motherTongue != null) {
      languageMessage += " (mother tongue: " + motherTongue.getShortCodeWithCountryAndVariant() + ")";
    }
    if (autoDetectLanguage) {
      languageMessage += "[auto]";
    }
    languageCheckCounts.put(lang.getShortCodeWithCountryAndVariant(), count);
    int computationTime = (int) (System.currentTimeMillis() - timeStart);
    Premium premium = Premium.get();

    List<String> premiumMatchRuleIds = res.stream().
            flatMap(r -> r.getRuleMatches().stream()).
            filter(k -> premium.isPremiumRule(k.getRule())).
            map(k -> k.getRule().getId()).
            collect(Collectors.toList());

    Map<String, Integer> ruleMatchCount = getRuleMatchCount(res);
    int matchCount = ruleMatchCount.size();

    String version = params.get("v") != null ? ", version: " + params.get("v") : "";
    String skipLimits = limits.getSkipLimits() ? ", skipLimits" : "";
    log.info("Check done: " + length + " chars, " + languageMessage +
            ", requestId: " + requestId + ", #" + count + ", " + referrer + ", "
            + premiumMatchRuleIds.size() + "/"
            + matchCount + " matches, "
            + computationTime + "ms, agent:" + agent + version
            + ", " + messageSent + ", q:" + (workQueue != null ? workQueue.size() : "?")
            + ", h:" + reqCounter.getHandleCount() + ", dH:" + reqCounter.getDistinctIps()
            + ", r:" + reqCounter.getRequestCount()
            + ", m:" + ServerTools.getModeForLog(mode) + skipLimits
            + ", premium: " + premiumStatus
            //+ ", temporaryPremiumDisabledRuleMatches: " + temporaryPremiumDisabledRuleMatch //TODO activate if used
            //+ ", temporaryPremiumDisabledRuleMatchedIds: " + temporaryPremiumDisabledRuleMatchedIds //TODO activate if used
            + (limits.getPremiumUid() != null ? ", uid:" + limits.getPremiumUid() : ""));
    if (premiumMatchRuleIds.size() > 0) {
      for (String premiumMatchRuleId : premiumMatchRuleIds) {
        log.info("premium:" + lang.getShortCodeWithCountryAndVariant() + ":" + premiumMatchRuleId);
      }
    }


    ServerMetricsCollector.getInstance().logCheck(
      lang, computationTime, textSize, matchCount, mode);

    if (!config.isSkipLoggingChecks()) {
      // NOTE: Java/DB (not sure) can't keep up with logging the volume of new entries we've reached,
      // so we limit it to enterprise customers where we actually pay attention to the request limits
      if (limits.getRequestsPerDay() != null) {
        DatabaseCheckLogEntry logEntry = new DatabaseCheckLogEntry(userId, agentId, logServerId, textSize, matchCount,
          lang, detLang.getDetectedLanguage(), computationTime, textSessionId, mode.toString());
        databaseLogger.log(logEntry);
      }
    }

    if (databaseLogger.isLogging()) {
      if (System.currentTimeMillis() - pingsCleanDateMillis > PINGS_CLEAN_MILLIS && pings.size() < PINGS_MAX_SIZE) {
        log.info("Cleaning pings DB (" + pings.size() + " items)");
        pings.clear();
        pingsCleanDateMillis = System.currentTimeMillis();
      }
      if (agentId != null && userId != null) {
        DatabasePingLogEntry ping = new DatabasePingLogEntry(agentId, userId);
        if (!pings.contains(ping)) {
          databaseLogger.log(ping);
          if (pings.size() >= PINGS_MAX_SIZE) {
            // prevent pings taking up unlimited amounts of memory
            log.warn("Pings DB has reached max size: " + pings.size());
          } else {
            pings.add(ping);
          }
        }
      }
    }
  }