SOLR-16883: Use postlogs tool from windows and unix via solr cli infrastructure. (#1786)
Postlogs tool only works on Linux, and was it's own implementation of a command line tool, with different patterns then the other tools. Migrate over to "bin/solr postlogs" and it now works like other CLI tools, including working on Windows.
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 2cd77f1..721d446 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -65,6 +65,8 @@
* SOLR-16490: `/admin/cores?action=backupcore` now has a v2 equivalent, available at
`GET /api/cores/coreName/backups` (Sanjay Dutt via Jason Gerlowski)
+* SOLR-16883: Postlogs tool for indexing Solr logs in Solr now supported on Windows by converting it to a Solr CLI command: `bin/solr postlogs`. `bin/postlogs` script marked deprected. (Eric Pugh, Will White)
+
Optimizations
---------------------
diff --git a/solr/bin/postlogs b/solr/bin/postlogs
index 71a5e7a..249f693 100755
--- a/solr/bin/postlogs
+++ b/solr/bin/postlogs
@@ -31,6 +31,7 @@
#
############################################################################################
+echo "This script has been deprecated in favour of 'bin/solr postlogs' command."
SOLR_TIP="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"/..
java -classpath "$SOLR_TIP/server/lib/ext/*:$SOLR_TIP/server/solr-webapp/webapp/WEB-INF/lib/*" org.apache.solr.cli.SolrLogPostTool $1 $2
diff --git a/solr/bin/solr b/solr/bin/solr
index 0905311..396de28 100644
--- a/solr/bin/solr
+++ b/solr/bin/solr
@@ -795,10 +795,10 @@
if [ $# -eq 1 ]; then
case $1 in
- -help|-h)
- run_tool ""
+ -help|-h)
+ run_tool ""
exit
- ;;
+ ;;
esac
fi
@@ -861,7 +861,7 @@
upconfig|downconfig|cp|rm|mv|ls|mkroot)
ZK_OP=$1
shift 1
- ;;
+ ;;
-z|-zkhost|-zkHost)
if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
print_short_zk_usage "$SCRIPT_CMD" "ZooKeeper connection string is required when using the $1 option!"
diff --git a/solr/bin/solr.cmd b/solr/bin/solr.cmd
index ae16c5d..7936998 100755
--- a/solr/bin/solr.cmd
+++ b/solr/bin/solr.cmd
@@ -247,6 +247,7 @@
IF "%1"=="create_core" goto run_solrcli
IF "%1"=="create_collection" goto run_solrcli
IF "%1"=="delete" goto run_solrcli
+IF "%1"=="postlogs" goto run_solrcli
IF "%1"=="zk" (
set SCRIPT_CMD=zk
SHIFT
@@ -281,6 +282,7 @@
IF "%SCRIPT_CMD%"=="zk" goto zk_usage
IF "%SCRIPT_CMD%"=="auth" goto auth_usage
IF "%SCRIPT_CMD%"=="status" goto run_solrcli
+IF "%SCRIPT_CMD%"=="postlogs" goto run_solrcli
goto done
:start_usage
diff --git a/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java b/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java
new file mode 100644
index 0000000..91e1a4e
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java
@@ -0,0 +1,606 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.solr.cli;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.PrintStream;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.impl.Http2SolrClient;
+import org.apache.solr.client.solrj.request.UpdateRequest;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.SolrInputField;
+import org.apache.solr.handler.component.ShardRequest;
+
+/** A command line tool for indexing Solr logs in the out-of-the-box log format. */
+public class PostLogsTool extends ToolBase {
+
+ public PostLogsTool() {
+ this(CLIO.getOutStream());
+ }
+
+ public PostLogsTool(PrintStream stdout) {
+ super(stdout);
+ }
+
+ @Override
+ public String getName() {
+ return "postlogs";
+ }
+
+ @Override
+ public List<Option> getOptions() {
+ return List.of(
+ Option.builder("url")
+ .longOpt("url")
+ .argName("ADDRESS")
+ .hasArg()
+ .required(true)
+ .desc("Address of the collection, example http://localhost:8983/solr/collection1/.")
+ .build(),
+ Option.builder("rootdir")
+ .longOpt("rootdir")
+ .argName("DIRECTORY")
+ .hasArg()
+ .required(true)
+ .desc("All files found at or below the root directory will be indexed.")
+ .build());
+ }
+
+ @Override
+ public void runImpl(CommandLine cli) throws Exception {
+ String url = cli.getOptionValue("url");
+ String rootDir = cli.getOptionValue("rootdir");
+ runCommand(url, rootDir);
+ }
+
+ public void runCommand(String baseUrl, String root) throws IOException {
+
+ Http2SolrClient.Builder builder = new Http2SolrClient.Builder(baseUrl);
+ try (SolrClient client = builder.build()) {
+ int rec = 0;
+ UpdateRequest request = new UpdateRequest();
+
+ List<Path> files;
+ try (Stream<Path> stream = Files.walk(Path.of(root), Integer.MAX_VALUE)) {
+ files = stream.filter(Files::isRegularFile).collect(Collectors.toList());
+ }
+
+ for (Path file : files) {
+ try (LineNumberReader reader =
+ new LineNumberReader(Files.newBufferedReader(file, Charset.defaultCharset()))) {
+ LogRecordReader recordReader = new LogRecordReader(reader);
+ SolrInputDocument doc;
+ String fileName = file.getFileName().toString();
+ while (true) {
+ try {
+ doc = recordReader.readRecord();
+ } catch (Throwable t) {
+ CLIO.err(
+ "Error reading log record:" + reader.getLineNumber() + " from file:" + fileName);
+ CLIO.err(t.getMessage());
+ continue;
+ }
+
+ if (doc == null) {
+ break;
+ }
+
+ rec++;
+ UUID id = UUID.randomUUID();
+ doc.setField("id", id.toString());
+ doc.setField("file_s", fileName);
+ request.add(doc);
+ if (rec == 300) {
+ sendBatch(client, request, false /* normal batch */);
+ request = new UpdateRequest();
+ rec = 0;
+ }
+ }
+ }
+ }
+
+ if (rec > 0) {
+ sendBatch(client, request, true /* last batch */);
+ }
+ }
+ }
+
+ private void sendBatch(SolrClient client, UpdateRequest request, boolean lastRequest) {
+ final String beginMessage =
+ lastRequest ? "Sending last batch ..." : "Sending batch of 300 log records...";
+ CLIO.out(beginMessage);
+ try {
+ request.process(client);
+ CLIO.out("Batch sent");
+ } catch (Exception e) {
+ CLIO.err("Batch sending failed: " + e.getMessage());
+ e.printStackTrace(CLIO.getErrStream());
+ }
+
+ if (lastRequest) {
+ try {
+ client.commit();
+ CLIO.out("Committed");
+ } catch (Exception e) {
+ CLIO.err("Unable to commit documents: " + e.getMessage());
+ e.printStackTrace(CLIO.getErrStream());
+ }
+ }
+ }
+
+ public static class LogRecordReader {
+
+ private BufferedReader bufferedReader;
+ private String pushedBack = null;
+ private boolean finished = false;
+ private String cause;
+ private Pattern p =
+ Pattern.compile("^(\\d\\d\\d\\d\\-\\d\\d\\-\\d\\d[\\s|T]\\d\\d:\\d\\d\\:\\d\\d.\\d\\d\\d)");
+ private Pattern minute =
+ Pattern.compile("^(\\d\\d\\d\\d\\-\\d\\d\\-\\d\\d[\\s|T]\\d\\d:\\d\\d)");
+ private Pattern tenSecond =
+ Pattern.compile("^(\\d\\d\\d\\d\\-\\d\\d\\-\\d\\d[\\s|T]\\d\\d:\\d\\d:\\d)");
+
+ public LogRecordReader(BufferedReader bufferedReader) throws IOException {
+ this.bufferedReader = bufferedReader;
+ }
+
+ public SolrInputDocument readRecord() throws IOException {
+ while (true) {
+ String line = null;
+
+ if (finished) {
+ return null;
+ }
+
+ if (pushedBack != null) {
+ line = pushedBack;
+ pushedBack = null;
+ } else {
+ line = bufferedReader.readLine();
+ }
+
+ if (line != null) {
+ SolrInputDocument lineDoc = new SolrInputDocument();
+ String date = parseDate(line);
+ String minute = parseMinute(line);
+ String tenSecond = parseTenSecond(line);
+ lineDoc.setField("date_dt", date);
+ lineDoc.setField("time_minute_s", minute);
+ lineDoc.setField("time_ten_second_s", tenSecond);
+ lineDoc.setField("line_t", line);
+ lineDoc.setField("type_s", "other"); // Overridden by known types below
+
+ if (line.contains("Registered new searcher")) {
+ parseNewSearch(lineDoc, line);
+ } else if (line.contains("path=/update")) {
+ parseUpdate(lineDoc, line);
+ } else if (line.contains(" ERROR ")) {
+ this.cause = null;
+ parseError(lineDoc, line, readTrace());
+ } else if (line.contains("QTime=")) {
+ parseQueryRecord(lineDoc, line);
+ }
+
+ return lineDoc;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private String readTrace() throws IOException {
+ StringBuilder buf = new StringBuilder();
+ buf.append("%html ");
+
+ while (true) {
+ String line = bufferedReader.readLine();
+ if (line == null) {
+ finished = true;
+ return buf.toString();
+ } else {
+ // look for a date at the beginning of the line
+ // If it's not there then read into the stack trace buffer
+ Matcher m = p.matcher(line);
+
+ if (!m.find() && buf.length() < 10000) {
+ // Line does not start with a timestamp so append to the stack trace
+ buf.append(line.replace("\t", " ")).append("<br/>");
+ if (line.startsWith("Caused by:")) {
+ this.cause = line;
+ }
+ } else {
+ pushedBack = line;
+ break;
+ }
+ }
+ }
+
+ return buf.toString();
+ }
+
+ private String parseDate(String line) {
+ Matcher m = p.matcher(line);
+ if (m.find()) {
+ String date = m.group(1);
+ return date.replace(" ", "T") + "Z";
+ }
+
+ return null;
+ }
+
+ private String parseMinute(String line) {
+ Matcher m = minute.matcher(line);
+ if (m.find()) {
+ String date = m.group(1);
+ return date.replace(" ", "T") + ":00Z";
+ }
+
+ return null;
+ }
+
+ private String parseTenSecond(String line) {
+ Matcher m = tenSecond.matcher(line);
+ if (m.find()) {
+ String date = m.group(1);
+ return date.replace(" ", "T") + "0Z";
+ }
+
+ return null;
+ }
+
+ private void setFieldIfUnset(SolrInputDocument doc, String fieldName, String fieldValue) {
+ if (doc.containsKey(fieldName)) return;
+
+ doc.setField(fieldName, fieldValue);
+ }
+
+ private void parseError(SolrInputDocument lineRecord, String line, String trace) {
+ lineRecord.setField("type_s", "error");
+
+ // Don't include traces that have only the %html header.
+ if (trace != null && trace.length() > 6) {
+ lineRecord.setField("stack_t", trace);
+ }
+
+ if (this.cause != null) {
+ lineRecord.setField("root_cause_t", cause.replace("Caused by:", "").trim());
+ }
+
+ lineRecord.setField("collection_s", parseCollection(line));
+ lineRecord.setField("core_s", parseCore(line));
+ lineRecord.setField("shard_s", parseShard(line));
+ lineRecord.setField("replica_s", parseReplica(line));
+ }
+
+ private void parseQueryRecord(SolrInputDocument lineRecord, String line) {
+ lineRecord.setField("qtime_i", parseQTime(line));
+ lineRecord.setField("status_s", parseStatus(line));
+
+ String path = parsePath(line);
+ lineRecord.setField("path_s", path);
+
+ if (line.contains("hits=")) {
+ lineRecord.setField("hits_l", parseHits(line));
+ }
+
+ String params = parseParams(line);
+ lineRecord.setField("params_t", params);
+ addParams(lineRecord, params);
+
+ lineRecord.setField("collection_s", parseCollection(line));
+ lineRecord.setField("core_s", parseCore(line));
+ lineRecord.setField("node_s", parseNode(line));
+ lineRecord.setField("shard_s", parseShard(line));
+ lineRecord.setField("replica_s", parseReplica(line));
+
+ if (path != null && path.contains("/admin")) {
+ lineRecord.setField("type_s", "admin");
+ } else if (path != null && params.contains("/replication")) {
+ lineRecord.setField("type_s", "replication");
+ } else if (path != null && path.contains("/get")) {
+ lineRecord.setField("type_s", "get");
+ } else {
+ lineRecord.setField("type_s", "query");
+ }
+ }
+
+ private void parseNewSearch(SolrInputDocument lineRecord, String line) {
+ lineRecord.setField("core_s", parseCore(line));
+ lineRecord.setField("type_s", "newSearcher");
+ lineRecord.setField("collection_s", parseCollection(line));
+ lineRecord.setField("shard_s", parseShard(line));
+ lineRecord.setField("replica_s", parseReplica(line));
+ }
+
+ private String parseCollection(String line) {
+ char[] ca = {' ', ']', ','};
+ String[] parts = line.split("c:");
+ if (parts.length >= 2) {
+ return readUntil(parts[1], ca);
+ } else {
+ return null;
+ }
+ }
+
+ private void parseUpdate(SolrInputDocument lineRecord, String line) {
+ if (line.contains("deleteByQuery=")) {
+ lineRecord.setField("type_s", "deleteByQuery");
+ } else if (line.contains("delete=")) {
+ lineRecord.setField("type_s", "delete");
+ } else if (line.contains("commit=true")) {
+ lineRecord.setField("type_s", "commit");
+ } else {
+ lineRecord.setField("type_s", "update");
+ }
+
+ lineRecord.setField("collection_s", parseCollection(line));
+ lineRecord.setField("core_s", parseCore(line));
+ lineRecord.setField("shard_s", parseShard(line));
+ lineRecord.setField("replica_s", parseReplica(line));
+ }
+
+ private String parseCore(String line) {
+ char[] ca = {' ', ']', '}', ','};
+ String[] parts = line.split("x:");
+ if (parts.length >= 2) {
+ return readUntil(parts[1], ca);
+ } else {
+ return null;
+ }
+ }
+
+ private String parseShard(String line) {
+ char[] ca = {' ', ']', '}', ','};
+ String[] parts = line.split("s:");
+ if (parts.length >= 2) {
+ return readUntil(parts[1], ca);
+ } else {
+ return null;
+ }
+ }
+
+ private String parseReplica(String line) {
+ char[] ca = {' ', ']', '}', ','};
+ String[] parts = line.split("r:");
+ if (parts.length >= 2) {
+ return readUntil(parts[1], ca);
+ } else {
+ return null;
+ }
+ }
+
+ private String parsePath(String line) {
+ char[] ca = {' '};
+ String[] parts = line.split(" path=");
+ if (parts.length == 2) {
+ return readUntil(parts[1], ca);
+ } else {
+ return null;
+ }
+ }
+
+ private String parseQTime(String line) {
+ char[] ca = {'\n', '\r'};
+ String[] parts = line.split(" QTime=");
+ if (parts.length == 2) {
+ return readUntil(parts[1], ca);
+ } else {
+ return null;
+ }
+ }
+
+ private String parseNode(String line) {
+ char[] ca = {' ', ']', '}', ','};
+ String[] parts = line.split("node_name=n:");
+ if (parts.length >= 2) {
+ return readUntil(parts[1], ca);
+ } else {
+ return null;
+ }
+ }
+
+ private String parseStatus(String line) {
+ char[] ca = {' ', '\n', '\r'};
+ String[] parts = line.split(" status=");
+ if (parts.length == 2) {
+ return readUntil(parts[1], ca);
+ } else {
+ return null;
+ }
+ }
+
+ private String parseHits(String line) {
+ char[] ca = {' '};
+ String[] parts = line.split(" hits=");
+ if (parts.length == 2) {
+ return readUntil(parts[1], ca);
+ } else {
+ return null;
+ }
+ }
+
+ private String parseParams(String line) {
+ char[] ca = {' '};
+ String[] parts = line.split(" params=");
+ if (parts.length == 2) {
+ String p = readUntil(parts[1].substring(1), ca);
+ return p.substring(0, p.length() - 1);
+ } else {
+ return null;
+ }
+ }
+
+ private String readUntil(String s, char[] chars) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < s.length(); i++) {
+ char a = s.charAt(i);
+ for (char c : chars) {
+ if (a == c) {
+ return builder.toString();
+ }
+ }
+ builder.append(a);
+ }
+
+ return builder.toString();
+ }
+
+ private void addParams(SolrInputDocument doc, String params) {
+ String[] pairs = params.split("&");
+ for (String pair : pairs) {
+ String[] parts = pair.split("=");
+ if (parts.length == 2 && parts[0].equals("q")) {
+ String dq = URLDecoder.decode(parts[1], Charset.defaultCharset());
+ setFieldIfUnset(doc, "q_s", dq);
+ setFieldIfUnset(doc, "q_t", dq);
+ }
+
+ if (parts[0].equals("rows")) {
+ String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
+ setFieldIfUnset(doc, "rows_i", dr);
+ }
+
+ if (parts[0].equals("start")) {
+ String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
+ setFieldIfUnset(doc, "start_i", dr);
+ }
+
+ if (parts[0].equals("distrib")) {
+ String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
+ setFieldIfUnset(doc, "distrib_s", dr);
+ }
+
+ if (parts[0].equals("shards")) {
+ setFieldIfUnset(doc, "shards_s", "true");
+ }
+
+ if (parts[0].equals("ids") && !isRTGRequest(doc)) {
+ setFieldIfUnset(doc, "ids_s", "true");
+ }
+
+ if (parts[0].equals("isShard")) {
+ String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
+ setFieldIfUnset(doc, "isShard_s", dr);
+ }
+
+ if (parts[0].equals("wt")) {
+ String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
+ setFieldIfUnset(doc, "wt_s", dr);
+ }
+
+ if (parts[0].equals("facet")) {
+ String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
+ setFieldIfUnset(doc, "facet_s", dr);
+ }
+
+ if (parts[0].equals("shards.purpose")) {
+ try {
+ int purpose = Integer.parseInt(parts[1]);
+ String[] purposes = getRequestPurposeNames(purpose);
+ for (String p : purposes) {
+ doc.addField("purpose_ss", p);
+ }
+ } catch (Throwable e) {
+ // We'll just sit on this for now and not interrupt the load for this one field.
+ }
+ }
+ }
+
+ // Special params used to determine what stage a query is.
+ // So we populate with defaults.
+ // The absence of the distrib params means it's a distributed query.
+ setFieldIfUnset(doc, "distrib_s", "true");
+ setFieldIfUnset(doc, "shards_s", "false");
+ setFieldIfUnset(doc, "ids_s", "false");
+ }
+
+ private boolean isRTGRequest(SolrInputDocument doc) {
+ final SolrInputField path = doc.getField("path_s");
+ if (path == null) return false;
+
+ return "/get".equals(path.getValue());
+ }
+ }
+
+ private static final Map<Integer, String> purposes;
+ protected static final String UNKNOWN_VALUE = "Unknown";
+ private static final String[] purposeUnknown = new String[] {UNKNOWN_VALUE};
+
+ public static String[] getRequestPurposeNames(Integer reqPurpose) {
+ if (reqPurpose != null) {
+ int valid = 0;
+ for (Map.Entry<Integer, String> entry : purposes.entrySet()) {
+ if ((reqPurpose & entry.getKey()) != 0) {
+ valid++;
+ }
+ }
+ if (valid == 0) {
+ return purposeUnknown;
+ } else {
+ String[] result = new String[valid];
+ int i = 0;
+ for (Map.Entry<Integer, String> entry : purposes.entrySet()) {
+ if ((reqPurpose & entry.getKey()) != 0) {
+ result[i] = entry.getValue();
+ i++;
+ }
+ }
+ return result;
+ }
+ }
+ return purposeUnknown;
+ }
+
+ static {
+ Map<Integer, String> map = new TreeMap<>();
+ map.put(ShardRequest.PURPOSE_PRIVATE, "PRIVATE");
+ map.put(ShardRequest.PURPOSE_GET_TOP_IDS, "GET_TOP_IDS");
+ map.put(ShardRequest.PURPOSE_REFINE_TOP_IDS, "REFINE_TOP_IDS");
+ map.put(ShardRequest.PURPOSE_GET_FACETS, "GET_FACETS");
+ map.put(ShardRequest.PURPOSE_REFINE_FACETS, "REFINE_FACETS");
+ map.put(ShardRequest.PURPOSE_GET_FIELDS, "GET_FIELDS");
+ map.put(ShardRequest.PURPOSE_GET_HIGHLIGHTS, "GET_HIGHLIGHTS");
+ map.put(ShardRequest.PURPOSE_GET_DEBUG, "GET_DEBUG");
+ map.put(ShardRequest.PURPOSE_GET_STATS, "GET_STATS");
+ map.put(ShardRequest.PURPOSE_GET_TERMS, "GET_TERMS");
+ map.put(ShardRequest.PURPOSE_GET_TOP_GROUPS, "GET_TOP_GROUPS");
+ map.put(ShardRequest.PURPOSE_GET_MLT_RESULTS, "GET_MLT_RESULTS");
+ map.put(ShardRequest.PURPOSE_REFINE_PIVOT_FACETS, "REFINE_PIVOT_FACETS");
+ map.put(ShardRequest.PURPOSE_SET_TERM_STATS, "SET_TERM_STATS");
+ map.put(ShardRequest.PURPOSE_GET_TERM_STATS, "GET_TERM_STATS");
+ purposes = Collections.unmodifiableMap(map);
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
index e6ff82d..8a92e68 100755
--- a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
+++ b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
@@ -237,6 +237,7 @@
else if ("export".equals(toolType)) return new ExportTool();
else if ("package".equals(toolType)) return new PackageTool();
else if ("post".equals(toolType)) return new PostTool();
+ else if ("postlogs".equals(toolType)) return new PostLogsTool();
else if ("version".equals(toolType)) return new VersionTool();
// If you add a built-in tool to this class, add it here to avoid
diff --git a/solr/core/src/java/org/apache/solr/cli/SolrLogPostTool.java b/solr/core/src/java/org/apache/solr/cli/SolrLogPostTool.java
index 8ee87a3..cf4fee6 100644
--- a/solr/core/src/java/org/apache/solr/cli/SolrLogPostTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/SolrLogPostTool.java
@@ -16,30 +16,12 @@
*/
package org.apache.solr.cli;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.LineNumberReader;
-import java.net.URLDecoder;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.UUID;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.impl.Http2SolrClient;
-import org.apache.solr.client.solrj.request.UpdateRequest;
-import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.common.SolrInputField;
-import org.apache.solr.handler.component.ShardRequest;
-
-/** A command line tool for indexing Solr logs in the out-of-the-box log format. */
+/**
+ * A command line tool for indexing Solr logs in the out-of-the-box log format.
+ *
+ * @deprecated Please use {@link PostLogsTool} that is exposed as 'bin/solr postlogs'.
+ */
+@Deprecated(since = "9.4")
public class SolrLogPostTool {
public static void main(String[] args) throws Exception {
@@ -60,524 +42,9 @@
CLIO.out("");
return;
}
-
String baseUrl = args[0];
String root = args[1];
-
- Http2SolrClient.Builder builder = new Http2SolrClient.Builder(baseUrl);
- try (SolrClient client = builder.build()) {
- int rec = 0;
- UpdateRequest request = new UpdateRequest();
-
- List<Path> files;
- try (Stream<Path> stream = Files.walk(Path.of(root), Integer.MAX_VALUE)) {
- files = stream.filter(Files::isRegularFile).collect(Collectors.toList());
- }
-
- for (Path file : files) {
- try (LineNumberReader reader =
- new LineNumberReader(Files.newBufferedReader(file, Charset.defaultCharset()))) {
- LogRecordReader recordReader = new LogRecordReader(reader);
- SolrInputDocument doc;
- String fileName = file.getFileName().toString();
- while (true) {
- try {
- doc = recordReader.readRecord();
- } catch (Throwable t) {
- CLIO.err(
- "Error reading log record:" + reader.getLineNumber() + " from file:" + fileName);
- CLIO.err(t.getMessage());
- continue;
- }
-
- if (doc == null) {
- break;
- }
-
- rec++;
- UUID id = UUID.randomUUID();
- doc.setField("id", id.toString());
- doc.setField("file_s", fileName);
- request.add(doc);
- if (rec == 300) {
- sendBatch(client, request, false /* normal batch */);
- request = new UpdateRequest();
- rec = 0;
- }
- }
- }
- }
-
- if (rec > 0) {
- sendBatch(client, request, true /* last batch */);
- }
- }
- }
-
- private static void sendBatch(SolrClient client, UpdateRequest request, boolean lastRequest) {
- final String beginMessage =
- lastRequest ? "Sending last batch ..." : "Sending batch of 300 log records...";
- CLIO.out(beginMessage);
- try {
- request.process(client);
- CLIO.out("Batch sent");
- } catch (Exception e) {
- CLIO.err("Batch sending failed: " + e.getMessage());
- e.printStackTrace(CLIO.getErrStream());
- }
-
- if (lastRequest) {
- try {
- client.commit();
- CLIO.out("Committed");
- } catch (Exception e) {
- CLIO.err("Unable to commit documents: " + e.getMessage());
- e.printStackTrace(CLIO.getErrStream());
- }
- }
- }
-
- public static class LogRecordReader {
-
- private BufferedReader bufferedReader;
- private String pushedBack = null;
- private boolean finished = false;
- private String cause;
- private Pattern p =
- Pattern.compile("^(\\d\\d\\d\\d\\-\\d\\d\\-\\d\\d[\\s|T]\\d\\d:\\d\\d\\:\\d\\d.\\d\\d\\d)");
- private Pattern minute =
- Pattern.compile("^(\\d\\d\\d\\d\\-\\d\\d\\-\\d\\d[\\s|T]\\d\\d:\\d\\d)");
- private Pattern tenSecond =
- Pattern.compile("^(\\d\\d\\d\\d\\-\\d\\d\\-\\d\\d[\\s|T]\\d\\d:\\d\\d:\\d)");
-
- public LogRecordReader(BufferedReader bufferedReader) throws IOException {
- this.bufferedReader = bufferedReader;
- }
-
- public SolrInputDocument readRecord() throws IOException {
- while (true) {
- String line = null;
-
- if (finished) {
- return null;
- }
-
- if (pushedBack != null) {
- line = pushedBack;
- pushedBack = null;
- } else {
- line = bufferedReader.readLine();
- }
-
- if (line != null) {
- SolrInputDocument lineDoc = new SolrInputDocument();
- String date = parseDate(line);
- String minute = parseMinute(line);
- String tenSecond = parseTenSecond(line);
- lineDoc.setField("date_dt", date);
- lineDoc.setField("time_minute_s", minute);
- lineDoc.setField("time_ten_second_s", tenSecond);
- lineDoc.setField("line_t", line);
- lineDoc.setField("type_s", "other"); // Overridden by known types below
-
- if (line.contains("Registered new searcher")) {
- parseNewSearch(lineDoc, line);
- } else if (line.contains("path=/update")) {
- parseUpdate(lineDoc, line);
- } else if (line.contains(" ERROR ")) {
- this.cause = null;
- parseError(lineDoc, line, readTrace());
- } else if (line.contains("QTime=")) {
- parseQueryRecord(lineDoc, line);
- }
-
- return lineDoc;
- } else {
- return null;
- }
- }
- }
-
- private String readTrace() throws IOException {
- StringBuilder buf = new StringBuilder();
- buf.append("%html ");
-
- while (true) {
- String line = bufferedReader.readLine();
- if (line == null) {
- finished = true;
- return buf.toString();
- } else {
- // look for a date at the beginning of the line
- // If it's not there then read into the stack trace buffer
- Matcher m = p.matcher(line);
-
- if (!m.find() && buf.length() < 10000) {
- // Line does not start with a timestamp so append to the stack trace
- buf.append(line.replace("\t", " ")).append("<br/>");
- if (line.startsWith("Caused by:")) {
- this.cause = line;
- }
- } else {
- pushedBack = line;
- break;
- }
- }
- }
-
- return buf.toString();
- }
-
- private String parseDate(String line) {
- Matcher m = p.matcher(line);
- if (m.find()) {
- String date = m.group(1);
- return date.replace(" ", "T") + "Z";
- }
-
- return null;
- }
-
- private String parseMinute(String line) {
- Matcher m = minute.matcher(line);
- if (m.find()) {
- String date = m.group(1);
- return date.replace(" ", "T") + ":00Z";
- }
-
- return null;
- }
-
- private String parseTenSecond(String line) {
- Matcher m = tenSecond.matcher(line);
- if (m.find()) {
- String date = m.group(1);
- return date.replace(" ", "T") + "0Z";
- }
-
- return null;
- }
-
- private void setFieldIfUnset(SolrInputDocument doc, String fieldName, String fieldValue) {
- if (doc.containsKey(fieldName)) return;
-
- doc.setField(fieldName, fieldValue);
- }
-
- private void parseError(SolrInputDocument lineRecord, String line, String trace) {
- lineRecord.setField("type_s", "error");
-
- // Don't include traces that have only the %html header.
- if (trace != null && trace.length() > 6) {
- lineRecord.setField("stack_t", trace);
- }
-
- if (this.cause != null) {
- lineRecord.setField("root_cause_t", cause.replace("Caused by:", "").trim());
- }
-
- lineRecord.setField("collection_s", parseCollection(line));
- lineRecord.setField("core_s", parseCore(line));
- lineRecord.setField("shard_s", parseShard(line));
- lineRecord.setField("replica_s", parseReplica(line));
- }
-
- private void parseQueryRecord(SolrInputDocument lineRecord, String line) {
- lineRecord.setField("qtime_i", parseQTime(line));
- lineRecord.setField("status_s", parseStatus(line));
-
- String path = parsePath(line);
- lineRecord.setField("path_s", path);
-
- if (line.contains("hits=")) {
- lineRecord.setField("hits_l", parseHits(line));
- }
-
- String params = parseParams(line);
- lineRecord.setField("params_t", params);
- addParams(lineRecord, params);
-
- lineRecord.setField("collection_s", parseCollection(line));
- lineRecord.setField("core_s", parseCore(line));
- lineRecord.setField("node_s", parseNode(line));
- lineRecord.setField("shard_s", parseShard(line));
- lineRecord.setField("replica_s", parseReplica(line));
-
- if (path != null && path.contains("/admin")) {
- lineRecord.setField("type_s", "admin");
- } else if (path != null && params.contains("/replication")) {
- lineRecord.setField("type_s", "replication");
- } else if (path != null && path.contains("/get")) {
- lineRecord.setField("type_s", "get");
- } else {
- lineRecord.setField("type_s", "query");
- }
- }
-
- private void parseNewSearch(SolrInputDocument lineRecord, String line) {
- lineRecord.setField("core_s", parseCore(line));
- lineRecord.setField("type_s", "newSearcher");
- lineRecord.setField("collection_s", parseCollection(line));
- lineRecord.setField("shard_s", parseShard(line));
- lineRecord.setField("replica_s", parseReplica(line));
- }
-
- private String parseCollection(String line) {
- char[] ca = {' ', ']', ','};
- String[] parts = line.split("c:");
- if (parts.length >= 2) {
- return readUntil(parts[1], ca);
- } else {
- return null;
- }
- }
-
- private void parseUpdate(SolrInputDocument lineRecord, String line) {
- if (line.contains("deleteByQuery=")) {
- lineRecord.setField("type_s", "deleteByQuery");
- } else if (line.contains("delete=")) {
- lineRecord.setField("type_s", "delete");
- } else if (line.contains("commit=true")) {
- lineRecord.setField("type_s", "commit");
- } else {
- lineRecord.setField("type_s", "update");
- }
-
- lineRecord.setField("collection_s", parseCollection(line));
- lineRecord.setField("core_s", parseCore(line));
- lineRecord.setField("shard_s", parseShard(line));
- lineRecord.setField("replica_s", parseReplica(line));
- }
-
- private String parseCore(String line) {
- char[] ca = {' ', ']', '}', ','};
- String[] parts = line.split("x:");
- if (parts.length >= 2) {
- return readUntil(parts[1], ca);
- } else {
- return null;
- }
- }
-
- private String parseShard(String line) {
- char[] ca = {' ', ']', '}', ','};
- String[] parts = line.split("s:");
- if (parts.length >= 2) {
- return readUntil(parts[1], ca);
- } else {
- return null;
- }
- }
-
- private String parseReplica(String line) {
- char[] ca = {' ', ']', '}', ','};
- String[] parts = line.split("r:");
- if (parts.length >= 2) {
- return readUntil(parts[1], ca);
- } else {
- return null;
- }
- }
-
- private String parsePath(String line) {
- char[] ca = {' '};
- String[] parts = line.split(" path=");
- if (parts.length == 2) {
- return readUntil(parts[1], ca);
- } else {
- return null;
- }
- }
-
- private String parseQTime(String line) {
- char[] ca = {'\n', '\r'};
- String[] parts = line.split(" QTime=");
- if (parts.length == 2) {
- return readUntil(parts[1], ca);
- } else {
- return null;
- }
- }
-
- private String parseNode(String line) {
- char[] ca = {' ', ']', '}', ','};
- String[] parts = line.split("node_name=n:");
- if (parts.length >= 2) {
- return readUntil(parts[1], ca);
- } else {
- return null;
- }
- }
-
- private String parseStatus(String line) {
- char[] ca = {' ', '\n', '\r'};
- String[] parts = line.split(" status=");
- if (parts.length == 2) {
- return readUntil(parts[1], ca);
- } else {
- return null;
- }
- }
-
- private String parseHits(String line) {
- char[] ca = {' '};
- String[] parts = line.split(" hits=");
- if (parts.length == 2) {
- return readUntil(parts[1], ca);
- } else {
- return null;
- }
- }
-
- private String parseParams(String line) {
- char[] ca = {' '};
- String[] parts = line.split(" params=");
- if (parts.length == 2) {
- String p = readUntil(parts[1].substring(1), ca);
- return p.substring(0, p.length() - 1);
- } else {
- return null;
- }
- }
-
- private String readUntil(String s, char[] chars) {
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < s.length(); i++) {
- char a = s.charAt(i);
- for (char c : chars) {
- if (a == c) {
- return builder.toString();
- }
- }
- builder.append(a);
- }
-
- return builder.toString();
- }
-
- private void addParams(SolrInputDocument doc, String params) {
- String[] pairs = params.split("&");
- for (String pair : pairs) {
- String[] parts = pair.split("=");
- if (parts.length == 2 && parts[0].equals("q")) {
- String dq = URLDecoder.decode(parts[1], Charset.defaultCharset());
- setFieldIfUnset(doc, "q_s", dq);
- setFieldIfUnset(doc, "q_t", dq);
- }
-
- if (parts[0].equals("rows")) {
- String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
- setFieldIfUnset(doc, "rows_i", dr);
- }
-
- if (parts[0].equals("start")) {
- String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
- setFieldIfUnset(doc, "start_i", dr);
- }
-
- if (parts[0].equals("distrib")) {
- String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
- setFieldIfUnset(doc, "distrib_s", dr);
- }
-
- if (parts[0].equals("shards")) {
- setFieldIfUnset(doc, "shards_s", "true");
- }
-
- if (parts[0].equals("ids") && !isRTGRequest(doc)) {
- setFieldIfUnset(doc, "ids_s", "true");
- }
-
- if (parts[0].equals("isShard")) {
- String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
- setFieldIfUnset(doc, "isShard_s", dr);
- }
-
- if (parts[0].equals("wt")) {
- String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
- setFieldIfUnset(doc, "wt_s", dr);
- }
-
- if (parts[0].equals("facet")) {
- String dr = URLDecoder.decode(parts[1], Charset.defaultCharset());
- setFieldIfUnset(doc, "facet_s", dr);
- }
-
- if (parts[0].equals("shards.purpose")) {
- try {
- int purpose = Integer.parseInt(parts[1]);
- String[] purposes = getRequestPurposeNames(purpose);
- for (String p : purposes) {
- doc.addField("purpose_ss", p);
- }
- } catch (Throwable e) {
- // We'll just sit on this for now and not interrupt the load for this one field.
- }
- }
- }
-
- // Special params used to determine what stage a query is.
- // So we populate with defaults.
- // The absence of the distrib params means it's a distributed query.
- setFieldIfUnset(doc, "distrib_s", "true");
- setFieldIfUnset(doc, "shards_s", "false");
- setFieldIfUnset(doc, "ids_s", "false");
- }
-
- private boolean isRTGRequest(SolrInputDocument doc) {
- final SolrInputField path = doc.getField("path_s");
- if (path == null) return false;
-
- return "/get".equals(path.getValue());
- }
- }
-
- private static final Map<Integer, String> purposes;
- protected static final String UNKNOWN_VALUE = "Unknown";
- private static final String[] purposeUnknown = new String[] {UNKNOWN_VALUE};
-
- public static String[] getRequestPurposeNames(Integer reqPurpose) {
- if (reqPurpose != null) {
- int valid = 0;
- for (Map.Entry<Integer, String> entry : purposes.entrySet()) {
- if ((reqPurpose & entry.getKey()) != 0) {
- valid++;
- }
- }
- if (valid == 0) {
- return purposeUnknown;
- } else {
- String[] result = new String[valid];
- int i = 0;
- for (Map.Entry<Integer, String> entry : purposes.entrySet()) {
- if ((reqPurpose & entry.getKey()) != 0) {
- result[i] = entry.getValue();
- i++;
- }
- }
- return result;
- }
- }
- return purposeUnknown;
- }
-
- static {
- Map<Integer, String> map = new TreeMap<>();
- map.put(ShardRequest.PURPOSE_PRIVATE, "PRIVATE");
- map.put(ShardRequest.PURPOSE_GET_TOP_IDS, "GET_TOP_IDS");
- map.put(ShardRequest.PURPOSE_REFINE_TOP_IDS, "REFINE_TOP_IDS");
- map.put(ShardRequest.PURPOSE_GET_FACETS, "GET_FACETS");
- map.put(ShardRequest.PURPOSE_REFINE_FACETS, "REFINE_FACETS");
- map.put(ShardRequest.PURPOSE_GET_FIELDS, "GET_FIELDS");
- map.put(ShardRequest.PURPOSE_GET_HIGHLIGHTS, "GET_HIGHLIGHTS");
- map.put(ShardRequest.PURPOSE_GET_DEBUG, "GET_DEBUG");
- map.put(ShardRequest.PURPOSE_GET_STATS, "GET_STATS");
- map.put(ShardRequest.PURPOSE_GET_TERMS, "GET_TERMS");
- map.put(ShardRequest.PURPOSE_GET_TOP_GROUPS, "GET_TOP_GROUPS");
- map.put(ShardRequest.PURPOSE_GET_MLT_RESULTS, "GET_MLT_RESULTS");
- map.put(ShardRequest.PURPOSE_REFINE_PIVOT_FACETS, "REFINE_PIVOT_FACETS");
- map.put(ShardRequest.PURPOSE_SET_TERM_STATS, "SET_TERM_STATS");
- map.put(ShardRequest.PURPOSE_GET_TERM_STATS, "GET_TERM_STATS");
- purposes = Collections.unmodifiableMap(map);
+ PostLogsTool postLogsTool = new PostLogsTool();
+ postLogsTool.runCommand(baseUrl, root);
}
}
diff --git a/solr/core/src/test/org/apache/solr/cli/SolrLogPostToolTest.java b/solr/core/src/test/org/apache/solr/cli/PostLogsToolTest.java
similarity index 98%
rename from solr/core/src/test/org/apache/solr/cli/SolrLogPostToolTest.java
rename to solr/core/src/test/org/apache/solr/cli/PostLogsToolTest.java
index 2ebfb9f..6240c54 100644
--- a/solr/core/src/test/org/apache/solr/cli/SolrLogPostToolTest.java
+++ b/solr/core/src/test/org/apache/solr/cli/PostLogsToolTest.java
@@ -22,20 +22,19 @@
import java.util.Collection;
import java.util.List;
import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.cli.SolrLogPostTool.LogRecordReader;
+import org.apache.solr.cli.PostLogsTool.LogRecordReader;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import org.junit.BeforeClass;
import org.junit.Test;
-public class SolrLogPostToolTest extends SolrTestCaseJ4 {
+public class PostLogsToolTest extends SolrTestCaseJ4 {
private static boolean solr9Format;
@BeforeClass
public static void beforeClass() {
solr9Format = random().nextBoolean();
- System.out.println("Solr 9 Format: " + solr9Format);
}
private String sometimesSolr9Format(String record) {
@@ -310,7 +309,7 @@
assertEquals(collection.getValue(), "test");
}
- // Ensure SolrLogPostTool parses _all_ log lines into searchable records
+ // Ensure PostLogsTool parses _all_ log lines into searchable records
@Test
public void testOtherRecord() throws Exception {
final String record =
diff --git a/solr/packaging/test/test_postlogs.bats b/solr/packaging/test/test_postlogs.bats
index 32f7c37..64c8230 100644
--- a/solr/packaging/test/test_postlogs.bats
+++ b/solr/packaging/test/test_postlogs.bats
@@ -38,7 +38,7 @@
delete_all_collections
}
-@test "post solr log into solr" {
+@test "post solr log into solr via script" {
run solr create_collection -c COLL_NAME
assert_output --partial "Created collection 'COLL_NAME'"
@@ -49,3 +49,15 @@
run curl 'http://localhost:8983/solr/COLL_NAME/select?q=*:*'
refute_output --partial '"numFound":0'
}
+
+@test "post solr log into solr via cli" {
+ run solr create_collection -c COLL_NAME
+ assert_output --partial "Created collection 'COLL_NAME'"
+
+ run solr postlogs -url http://localhost:8983/solr/COLL_NAME -rootdir ${SOLR_LOGS_DIR}/solr.log
+ assert_output --partial 'Sending last batch'
+ assert_output --partial 'Committed'
+
+ run curl 'http://localhost:8983/solr/COLL_NAME/select?q=*:*'
+ refute_output --partial '"numFound":0'
+}
diff --git a/solr/solr-ref-guide/modules/query-guide/pages/logs.adoc b/solr/solr-ref-guide/modules/query-guide/pages/logs.adoc
index 7b94125..7ac0255 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/logs.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/logs.adoc
@@ -24,25 +24,41 @@
== Loading
-The out-of-the-box Solr log format can be loaded into a Solr index using the `bin/postlogs` command line tool located in the `bin/` directory of the Solr distribution.
+The out-of-the-box Solr log format can be loaded into a Solr index using the `bin/solr postlogs` command line tool located in the `bin/` directory of the Solr distribution.
-NOTE: If working from the source distribution the
-distribution must first be built before `postlogs` can be run.
+=== Postlogs
-The `postlogs` script is designed to be run from the root directory of the Solr distribution.
+The `postlogs` command reads in Solr's log format and indexes it in a Solr collection.
-The `postlogs` script takes two parameters:
+`bin/solr postlogs [options]`
-* Solr base URL (with collection): `http://localhost:8983/solr/logs`
-* File path to root of the logs directory: All files found under this directory (including sub-directories) will be indexed.
+`bin/solr postlogs -help`
+
+==== Healthcheck Parameters
+
+`-url <ADDRESS>`::
++
+[%autowidth,frame=none]
+|===
+|Required |Default: none
+|===
++
+Address of the collection, example http://localhost:8983/solr/collection1/.
++
+
+`-rootdir <DIRECTORY>`::
++
+[%autowidth,frame=none]
+|===
+|Required |Default: none
+|===
++
+File path to root of the logs directory: All files found under this directory (including sub-directories) will be indexed.
If the path points to a single log file only that log file will be loaded.
-Below is a sample execution of the `postlogs` tool:
++
+*Example*: `bin/solr postlogs --url http://localhost:8983/solr/logs --rootdir /var/logs/solrlogs`
-[source,text]
-----
-./bin/postlogs http://localhost:8983/solr/logs /var/logs/solrlogs
-----
The example above will index all the log files under `/var/logs/solrlogs` to the `logs` collection found at the base url `http://localhost:8983/solr`.