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}