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