com.emc.mongoose.base.control.logs.LogServlet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mongoose-base Show documentation
Show all versions of mongoose-base Show documentation
Mongoose is a high-load storage performance testing tool
package com.emc.mongoose.base.control.logs;
import static com.emc.mongoose.base.Constants.MIB;
import static com.github.akurilov.commons.lang.Exceptions.throwUnchecked;
import static org.eclipse.jetty.server.InclusiveByteRange.satisfiableRanges;
import com.emc.mongoose.base.Constants;
import com.emc.mongoose.base.logging.Loggers;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender;
import org.apache.logging.log4j.core.async.AsyncLogger;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.InclusiveByteRange;
public final class LogServlet extends HttpServlet {
private static final String KEY_STEP_ID = "stepId";
private static final String KEY_LOGGER_NAME = "loggerName";
private static final Pattern PATTERN_URI_PATH = Pattern.compile(
"/logs/(?<" + KEY_STEP_ID + ">[\\w\\-_.,;:~=+@]+)/(?<" + KEY_LOGGER_NAME + ">[\\w_.]+)");
private static final String PATTERN_STEP_ID_SUBST = "${ctx:" + Constants.KEY_STEP_ID + "}";
private static final int LOG_PAGE_SIZE_LIMIT = MIB;
private final Map logFileNamePatternByName;
public LogServlet() {
logFileNamePatternByName = Arrays.stream(Loggers.class.getFields())
.map(
field -> {
try {
return field.get(null);
} catch (final Exception e) {
throw new AssertionError(e);
}
})
.filter(fieldVal -> fieldVal instanceof Logger)
.map(o -> (Logger) o)
.filter(logger -> logger.getName().startsWith(Loggers.BASE))
.collect(
Collectors.toMap(
logger -> logger.getName().substring(Loggers.BASE.length()),
logger -> ((AsyncLogger) logger)
.getAppenders().values().stream()
.filter(
appender -> appender instanceof RollingRandomAccessFileAppender)
.map(
appender -> ((RollingRandomAccessFileAppender) appender)
.getFilePattern())
.findAny()
.orElse("")));
}
@Override
protected final void doGet(final HttpServletRequest req, final HttpServletResponse resp)
throws IOException {
try {
logFilePath(req)
.ifPresentOrElse(
path -> {
try {
try {
respondFileContent(path, req, resp);
resp.setStatus(HttpStatus.OK_200);
} catch(final MultipleByteRangesException e) {
resp.sendError(HttpStatus.RANGE_NOT_SATISFIABLE_416, e.getMessage());
}
} catch(final Exception e) {
throwUnchecked(e);
}
},
() -> {
try {
writeLogNamesJson(resp.getOutputStream());
resp.setStatus(HttpStatus.OK_200);
} catch(final IOException e) {
throwUnchecked(e);
}
}
);
} catch(final NoLoggerException | InvalidUriPathException e) {
resp.sendError(HttpStatus.BAD_REQUEST_400, e.getMessage());
} catch(final NoLogFileException e) {
resp.sendError(HttpStatus.NOT_FOUND_404, e.getMessage());
}
}
@Override
protected final void doDelete(final HttpServletRequest req, final HttpServletResponse resp)
throws IOException {
try {
logFilePath(req)
.ifPresentOrElse(
path -> {
try {
Files.delete(path);
resp.setStatus(HttpStatus.OK_200);
} catch(final IOException e) {
resp.setStatus(
HttpStatus.INTERNAL_SERVER_ERROR_500, "Failed to delete the log file"
);
}
},
() -> {
try {
resp.sendError(HttpStatus.BAD_REQUEST_400);
} catch(final IOException e) {
throwUnchecked(e);
}
}
);
} catch (final NoLoggerException | InvalidUriPathException e) {
resp.sendError(HttpStatus.BAD_REQUEST_400, e.getMessage());
} catch (final NoLogFileException e) {
resp.sendError(HttpStatus.NOT_FOUND_404, e.getMessage());
}
}
private Optional logFilePath(final HttpServletRequest req)
throws NoLoggerException, NoLogFileException, InvalidUriPathException {
final String reqUri = req.getRequestURI();
final Matcher matcher = PATTERN_URI_PATH.matcher(reqUri);
if (matcher.find()) {
final String stepId = matcher.group(KEY_STEP_ID);
final String loggerName = matcher.group(KEY_LOGGER_NAME);
final String logFileNamePattern = logFileNamePatternByName.get(loggerName);
if (null == logFileNamePattern) {
throw new NoLoggerException("No such logger: \"" + loggerName + "\"");
} else if (logFileNamePattern.isEmpty()) {
throw new NoLogFileException(
"Unable to determine the log file for the logger \"" + loggerName + "\"");
} else {
final String logFile = logFileNamePattern.replace(PATTERN_STEP_ID_SUBST, stepId);
return Optional.of(Paths.get(logFile));
}
} else {
return Optional.empty();
}
}
private static void respondFileContent(
final Path filePath, final HttpServletRequest req, final HttpServletResponse resp)
throws IOException, MultipleByteRangesException, NoLogFileException {
if (Files.exists(filePath)) {
final Enumeration rangeHeaders = req.getHeaders(HttpHeader.RANGE.asString());
final List byteRanges = satisfiableRanges(rangeHeaders, Files.size(filePath));
final long offset;
final long size;
if (byteRanges == null || 0 == byteRanges.size()) {
offset = 0;
size = LOG_PAGE_SIZE_LIMIT - 1;
} else if (1 == byteRanges.size()) {
final InclusiveByteRange byteRange = byteRanges.get(0);
offset = byteRange.getFirst();
size = byteRange.getLast() + 1;
} else {
throw new MultipleByteRangesException("Unable to process more than 1 range header");
}
writeFileRange(filePath, offset, size, resp.getOutputStream(), resp.getBufferSize());
} else {
throw new NoLogFileException("The log file doesn't exist");
}
}
private static void writeFileRange(
final Path filePath,
final long offset,
final long size,
final OutputStream out,
final int buffSize)
throws IOException {
final int sizeLimit = (int) Math.min(size, LOG_PAGE_SIZE_LIMIT);
try (final InputStream in = Files.newInputStream(filePath)) {
long skipped = 0;
while (offset > skipped) {
skipped += in.skip(offset - skipped);
}
final byte[] buff = new byte[Math.min(sizeLimit, buffSize)];
long done = 0;
int n;
while (done < sizeLimit && -1 != (n = in.read(buff))) {
out.write(buff, 0, n);
done += n;
}
}
}
private static void writeLogNamesJson(final OutputStream out)
throws IOException {
final var jsonFactory = new JsonFactory();
final var mapper = new ObjectMapper(jsonFactory);
mapper
.configure(SerializationFeature.INDENT_OUTPUT, true)
.configure(Feature.AUTO_CLOSE_TARGET, false)
.configure(Feature.FLUSH_PASSED_TO_STREAM, false)
.writerWithDefaultPrettyPrinter()
.writeValue(out, Loggers.DESCRIPTIONS_BY_NAME);
out.write(System.lineSeparator().getBytes());
out.flush();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy