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            try (OperationContext ctx = new OperationContext()) {
188                ctx.setInput(blob);
189                Framework.getService(AutomationService.class).run(ctx, chain);
190            }
191        }
192    }
193
194    public void flushCache() {
195        initialLogLines = null;
196        selectedLogFileLastModified = -1;
197        bytesRead = 0;
198    }
199
200    public static class LogLine {
201
202        public enum Status {
203            INFO, DEBUG, WARN, ERROR, UNKNOWN
204        }
205
206        protected String line;
207
208        protected Status status;
209
210        public LogLine(String line) {
211            this.line = line;
212            status = computeStatus(line);
213        }
214
215        public String getLine() {
216            return line;
217        }
218
219        public Status getStatus() {
220            return status;
221        }
222
223        protected static Status computeStatus(String line) {
224            Status status = Status.UNKNOWN;
225            if (line.matches(".* INFO .*")) {
226                status = Status.INFO;
227            } else if (line.matches(".* DEBUG .*")) {
228                status = Status.DEBUG;
229            } else if (line.matches(".* WARN .*")) {
230                status = Status.WARN;
231            } else if (line.matches(".* ERROR .*")) {
232                status = Status.ERROR;
233            }
234            return status;
235        }
236
237    }
238
239}