001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Thomas Roger 016 */ 017 018package org.nuxeo.logs.viewer; 019 020import java.io.BufferedReader; 021import java.io.File; 022import java.io.FileReader; 023import java.io.IOException; 024import java.io.Serializable; 025import java.util.ArrayList; 026import java.util.List; 027 028import org.apache.commons.io.FilenameUtils; 029import org.apache.commons.io.input.ReversedLinesFileReader; 030import org.jboss.seam.ScopeType; 031import org.jboss.seam.annotations.Factory; 032import org.jboss.seam.annotations.Install; 033import org.jboss.seam.annotations.Name; 034import org.jboss.seam.annotations.Scope; 035import org.nuxeo.ecm.automation.AutomationService; 036import org.nuxeo.ecm.automation.OperationChain; 037import org.nuxeo.ecm.automation.OperationContext; 038import org.nuxeo.ecm.automation.OperationException; 039import org.nuxeo.ecm.automation.jsf.operations.DownloadFile; 040import org.nuxeo.ecm.core.api.Blob; 041import org.nuxeo.ecm.core.api.Blobs; 042import org.nuxeo.launcher.config.ConfigurationGenerator; 043import org.nuxeo.runtime.api.Framework; 044import org.nuxeo.runtime.services.config.ConfigurationService; 045 046/** 047 * @since 6.0 048 */ 049@Scope(ScopeType.CONVERSATION) 050@Name("logsViewerActions") 051@Install(precedence = Install.FRAMEWORK) 052public class LogsViewerActions implements Serializable { 053 054 private static final long serialVersionUID = 1L; 055 056 public static final String LOG_MAX_LINES_COUNT_KEY = "nuxeo.logs.viewer.max.lines.count"; 057 058 public static final int LOG_MAX_LINES_COUNT = 1500; 059 060 protected long logMaxLinesCount = -1; 061 062 protected List<String> logFiles; 063 064 protected String selectedLogFile; 065 066 protected long selectedLogFileLastModified = -1; 067 068 protected List<LogLine> initialLogLines; 069 070 protected long bytesRead = 0; 071 072 public long getLogMaxLinesCount() { 073 if (logMaxLinesCount == -1) { 074 ConfigurationService cs = Framework.getService(ConfigurationService.class); 075 logMaxLinesCount = Integer.parseInt( 076 cs.getProperty(LOG_MAX_LINES_COUNT_KEY, String.valueOf(LOG_MAX_LINES_COUNT)), 10); 077 } 078 return logMaxLinesCount; 079 } 080 081 public List<String> getLogFiles() { 082 if (logFiles == null) { 083 ConfigurationGenerator configurationGenerator = new ConfigurationGenerator(); 084 configurationGenerator.init(); 085 086 logFiles = new ArrayList<>(configurationGenerator.getLogFiles()); 087 088 // Add nuxeoctl log file 089 File nuxeoctlLog = new File(configurationGenerator.getLogDir(), "nuxeoctl.log"); 090 if (nuxeoctlLog.exists()) { 091 logFiles.add(nuxeoctlLog.getAbsolutePath()); 092 } 093 // Add console log file 094 File consoleLog = new File(configurationGenerator.getLogDir(), "console.log"); 095 if (consoleLog.exists()) { 096 logFiles.add(consoleLog.getAbsolutePath()); 097 } 098 } 099 return logFiles; 100 } 101 102 public String getSelectedLogFile() { 103 if (selectedLogFile == null) { 104 List<String> logFiles = getLogFiles(); 105 if (!logFiles.isEmpty()) { 106 selectedLogFile = logFiles.get(0); 107 selectedLogFileLastModified = -1; 108 } 109 } 110 return selectedLogFile; 111 } 112 113 public void setSelectedLogFile(String selectedLogFile) { 114 flushCache(); 115 this.selectedLogFile = selectedLogFile; 116 } 117 118 public String getFileName(String logFile) { 119 return FilenameUtils.getName(logFile); 120 } 121 122 public List<LogLine> getInitialLogLines() throws IOException { 123 if (initialLogLines == null) { 124 initialLogLines = new ArrayList<>(); 125 String selectedLogFile = getSelectedLogFile(); 126 if (selectedLogFile != null) { 127 File logFile = new File(selectedLogFile); 128 if (logFile.exists()) { 129 try (ReversedLinesFileReader reversedLinesFileReader = new ReversedLinesFileReader(new File( 130 getSelectedLogFile()))) { 131 for (int i = 0; i < getLogMaxLinesCount(); i++) { 132 String line = reversedLinesFileReader.readLine(); 133 if (line != null) { 134 initialLogLines.add(0, new LogLine(line)); 135 } else { 136 break; 137 } 138 } 139 } 140 bytesRead = logFile.length(); 141 selectedLogFileLastModified = logFile.lastModified(); 142 } 143 } 144 } 145 return initialLogLines; 146 } 147 148 @Factory(value = "newLogLines", scope = ScopeType.EVENT) 149 public List<LogLine> getNewLogLines() throws IOException { 150 List<LogLine> logLines = new ArrayList<>(); 151 String selectedLogFile = getSelectedLogFile(); 152 if (selectedLogFile != null) { 153 File logFile = new File(selectedLogFile); 154 if (logFile.exists() && logFile.lastModified() > selectedLogFileLastModified) { 155 if (bytesRead > logFile.length()) { 156 // log rotation 157 bytesRead = 0; 158 } 159 160 try (BufferedReader in = new BufferedReader(new FileReader(logFile))) { 161 in.skip(bytesRead); 162 String line; 163 while ((line = in.readLine()) != null) { 164 logLines.add(new LogLine(line)); 165 } 166 } 167 bytesRead = logFile.length(); 168 selectedLogFileLastModified = logFile.lastModified(); 169 } 170 } 171 return logLines; 172 } 173 174 public void downloadLogFile() throws IOException, OperationException { 175 String selectedLogFile = getSelectedLogFile(); 176 if (selectedLogFile == null) { 177 return; 178 } 179 180 File logFile = new File(selectedLogFile); 181 if (logFile.exists()) { 182 Blob blob = Blobs.createBlob(logFile); 183 OperationChain chain = new OperationChain("DownloadServerLogFile"); 184 chain.add(DownloadFile.ID); 185 OperationContext ctx = new OperationContext(); 186 ctx.setInput(blob); 187 Framework.getLocalService(AutomationService.class).run(ctx, chain); 188 } 189 } 190 191 public void flushCache() { 192 initialLogLines = null; 193 selectedLogFileLastModified = -1; 194 bytesRead = 0; 195 } 196 197 public static class LogLine { 198 199 public enum Status { 200 INFO, DEBUG, WARN, ERROR, UNKNOWN 201 } 202 203 protected String line; 204 205 protected Status status; 206 207 public LogLine(String line) { 208 this.line = line; 209 this.status = computeStatus(line); 210 } 211 212 public String getLine() { 213 return line; 214 } 215 216 public Status getStatus() { 217 return status; 218 } 219 220 protected static Status computeStatus(String line) { 221 Status status = Status.UNKNOWN; 222 if (line.matches(".* INFO .*")) { 223 status = Status.INFO; 224 } else if (line.matches(".* DEBUG .*")) { 225 status = Status.DEBUG; 226 } else if (line.matches(".* WARN .*")) { 227 status = Status.WARN; 228 } else if (line.matches(".* ERROR .*")) { 229 status = Status.ERROR; 230 } 231 return status; 232 } 233 234 } 235 236}