/* * 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.ignite.internal.visor.log; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import org.apache.ignite.IgniteException; import org.apache.ignite.compute.ComputeJobResult; import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.util.io.GridReversedLinesFileReader; import org.apache.ignite.internal.util.lang.GridTuple3; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorMultiNodeTask; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.visor.util.VisorTaskUtils.decode; import static org.apache.ignite.internal.visor.util.VisorTaskUtils.matchedFiles; import static org.apache.ignite.internal.visor.util.VisorTaskUtils.textFile; /** * Search text matching in logs */ @GridInternal public class VisorLogSearchTask extends VisorMultiNodeTask<VisorLogSearchTaskArg, VisorLogSearchTaskResult, Collection<VisorLogSearchResult>> { /** */ private static final long serialVersionUID = 0L; /** How many lines to read around line with found text. */ public static final int LINE_CNT = 21; /** How many lines should be read before and after line with found text. */ public static final int HALF = LINE_CNT / 2; /** {@inheritDoc} */ @Override protected VisorLogSearchJob job(VisorLogSearchTaskArg arg) { return new VisorLogSearchJob(arg, debug); } /** {@inheritDoc} */ @Nullable @Override protected VisorLogSearchTaskResult reduce0(List<ComputeJobResult> results) { List<VisorLogSearchResult> searchRes = new ArrayList<>(); Map<Exception, UUID> exRes = U.newHashMap(0); // Separate successfully executed results and exceptions. for (ComputeJobResult result : results) { if (result.getException() != null) exRes.put(result.getException(), result.getNode().id()); else if (result.getData() != null) { Collection<VisorLogSearchResult> data = result.getData(); searchRes.addAll(data); } } return new VisorLogSearchTaskResult(exRes.isEmpty() ? null : exRes, searchRes.isEmpty() ? null : searchRes); } /** * Job to perform search on node. */ private static class VisorLogSearchJob extends VisorJob<VisorLogSearchTaskArg, Collection<VisorLogSearchResult>> { /** */ private static final long serialVersionUID = 0L; /** * @param arg Search descriptor. * @param debug Debug flag. */ private VisorLogSearchJob(VisorLogSearchTaskArg arg, boolean debug) { super(arg, debug); } /** * @param f File to read. * @param charset Text charset. * @param searchStr Search string. * @param limit Max number of search results. * @return Collection of found descriptors. * @throws IOException In case of I/O error. */ private List<GridTuple3<String[], Integer, Integer>> searchInFile(File f, Charset charset, String searchStr, int limit) throws IOException { List<GridTuple3<String[], Integer, Integer>> searched = new ArrayList<>(); int line = 0; try (GridReversedLinesFileReader reader = new GridReversedLinesFileReader(f, 4096, charset)) { Deque<String> lastLines = new LinkedList<>(); String s; int lastFoundLine = 0, foundCnt = 0; while ((s = reader.readLine()) != null) { line++; if (lastFoundLine > 0 && line - lastFoundLine <= HALF) { for (int i = searched.size() - 1; i >= 0; i--) { GridTuple3<String[], Integer, Integer> tup = searched.get(i); int delta = line - tup.get2(); if (delta <= HALF && delta != 0) tup.get1()[HALF - delta] = s; else break; } } if (foundCnt < limit) { if (s.toLowerCase().contains(searchStr)) { String[] buf = new String[LINE_CNT]; buf[HALF] = s; int i = 1; for (String l : lastLines) { buf[HALF + i] = l; i++; } lastFoundLine = line; searched.add(new GridTuple3<>(buf, line, 0)); foundCnt++; } } if (lastLines.size() >= HALF) lastLines.removeFirst(); lastLines.add(s); } } for (GridTuple3<String[], Integer, Integer> entry : searched) { entry.set2(line - entry.get2() + 1); entry.set3(line); } return searched; } /** {@inheritDoc} */ @Override protected Collection<VisorLogSearchResult> run(VisorLogSearchTaskArg arg) { URL url = U.resolveIgniteUrl(arg.getFolder()); if (url == null) throw new IgniteException(new FileNotFoundException("Log folder not found: " + arg.getFolder())); UUID uuid = ignite.localNode().id(); String nid = uuid.toString().toLowerCase(); String filePtrn = arg.getFilePattern().replace("@nid8", nid.substring(0, 8)).replace("@nid", nid); try { File fld = new File(url.toURI()); int pathIdx = (fld.isDirectory() ? fld : fld.getParentFile()).getAbsolutePath().length() + 1; List<VisorLogFile> matchingFiles = matchedFiles(fld, filePtrn); Collection<VisorLogSearchResult> results = new ArrayList<>(arg.getLimit()); int resCnt = 0; for (VisorLogFile logFile : matchingFiles) { try { File f = new File(logFile.getPath()); if (textFile(f, false)) { Charset charset = decode(f); if (resCnt == arg.getLimit()) break; List<GridTuple3<String[], Integer, Integer>> searched = searchInFile(f, charset, arg.getSearchString(), arg.getLimit() - resCnt); resCnt += searched.size(); String path = f.getAbsolutePath().substring(pathIdx); long sz = f.length(), lastModified = f.lastModified(); for (GridTuple3<String[], Integer, Integer> e : searched) { results.add(new VisorLogSearchResult(uuid, path, sz, lastModified, e.get1(), e.get2(), e.get3(), charset.name())); } } } catch (IOException ignored) { } } return results.isEmpty() ? null : results; } catch (Exception e) { throw new IgniteException(e); } } /** {@inheritDoc} */ @Override public String toString() { return S.toString(VisorLogSearchJob.class, this); } } }