/** * **************************************************************************** * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.drill.exec.server.rest; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import org.apache.drill.common.exceptions.DrillRuntimeException; import org.apache.drill.exec.ExecConstants; import org.apache.drill.exec.work.WorkManager; import org.glassfish.jersey.server.mvc.Viewable; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.xml.bind.annotation.XmlRootElement; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import static org.apache.drill.exec.server.rest.auth.DrillUserPrincipal.ADMIN_ROLE; @Path("/") @RolesAllowed(ADMIN_ROLE) public class LogsResources { @Inject DrillRestServer.UserAuthEnabled authEnabled; @Inject SecurityContext sc; @Inject WorkManager work; private static final FileFilter file_filter = new FileFilter() { @Override public boolean accept(File file) { return file.isFile(); } }; private static final DateTimeFormatter format = DateTimeFormat.forPattern("MM/dd/yyyy HH:mm:ss"); @GET @Path("/logs") @Produces(MediaType.TEXT_HTML) public Viewable getLogs() { Set<Log> logs = getLogsJSON(); return ViewableWithPermissions.create(authEnabled.get(), "/rest/logs/list.ftl", sc, logs); } @GET @Path("/logs.json") @Produces(MediaType.APPLICATION_JSON) public Set<Log> getLogsJSON() { Set<Log> logs = Sets.newTreeSet(); File[] files = getLogFolder().listFiles(file_filter); for (File file : files) { logs.add(new Log(file.getName(), file.length(), file.lastModified())); } return logs; } @GET @Path("/log/{name}/content") @Produces(MediaType.TEXT_HTML) public Viewable getLog(@PathParam("name") String name) throws IOException { LogContent content = getLogJSON(name); return ViewableWithPermissions.create(authEnabled.get(), "/rest/logs/log.ftl", sc, content); } @GET @Path("/log/{name}/content.json") @Produces(MediaType.APPLICATION_JSON) public LogContent getLogJSON(@PathParam("name") final String name) throws IOException { File file = getFileByName(getLogFolder(), name); final int maxLines = work.getContext().getOptionManager().getOption(ExecConstants.WEB_LOGS_MAX_LINES).num_val.intValue(); try (BufferedReader br = new BufferedReader(new FileReader(file))) { Map<Integer, String> cache = new LinkedHashMap<Integer, String>(maxLines, .75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest) { return size() > maxLines; } }; String line; int i = 0; while ((line = br.readLine()) != null) { cache.put(i++, line); } return new LogContent(file.getName(), cache.values(), maxLines); } } @GET @Path("/log/{name}/download") @Produces(MediaType.TEXT_PLAIN) public Response getFullLog(@PathParam("name") final String name) { File file = getFileByName(getLogFolder(), name); Response.ResponseBuilder response = Response.ok(file); response.header("Content-Disposition", String.format("attachment;filename=\"%s\"", name)); return response.build(); } private File getLogFolder() { return new File(Preconditions.checkNotNull(System.getenv("DRILL_LOG_DIR"), "DRILL_LOG_DIR variable is not set")); } private File getFileByName(File folder, final String name) { File[] files = folder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String fileName) { return fileName.equals(name); } }); if (files.length == 0) { throw new DrillRuntimeException (name + " doesn't exist"); } return files[0]; } @XmlRootElement public class Log implements Comparable<Log> { private String name; private long size; private DateTime lastModified; @JsonCreator public Log (@JsonProperty("name") String name, @JsonProperty("size") long size, @JsonProperty("lastModified") long lastModified) { this.name = name; this.size = size; this.lastModified = new DateTime(lastModified); } public String getName() { return name; } public String getSize() { return Math.ceil(size / 1024d) + " KB"; } public String getLastModified() { return lastModified.toString(format); } @Override public int compareTo(Log log) { return this.getName().compareTo(log.getName()); } } @XmlRootElement public class LogContent { private String name; private Collection<String> lines; private int maxLines; @JsonCreator public LogContent (@JsonProperty("name") String name, @JsonProperty("lines") Collection<String> lines, @JsonProperty("maxLines") int maxLines) { this.name = name; this.lines = lines; this.maxLines = maxLines; } public String getName() { return name; } public Collection<String> getLines() { return lines; } public int getMaxLines() { return maxLines; } } }