/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 net.java.sip.communicator.plugin.loggingutils; import java.io.*; import java.text.*; import java.util.*; import java.util.regex.*; import java.util.zip.*; import net.java.sip.communicator.util.Logger; import org.jitsi.service.fileaccess.*; import org.jitsi.util.*; /** * Collects logs and save them in compressed zip file. * @author Damian Minkov */ public class LogsCollector { /** * Our Logger. */ private static final Logger logger = Logger.getLogger(LogsCollector.class); /** * The name of the log dir. */ final static String LOGGING_DIR_NAME = "log"; /** * The prefix name of standard java crash log file. */ private static final String JAVA_ERROR_LOG_PREFIX = "hs_err_pid"; /** * The date format used in file names. */ private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd@HH.mm.ss"); /** * The pattern we use to match crash logs. */ private static Pattern JAVA_ERROR_LOG_PATTERN = Pattern.compile( Pattern.quote("sip.communicator"), Pattern.CASE_INSENSITIVE); /** * Save the log files in archived file. If destination is a folder * we generate filename with current date and time. If the destination * is null we do nothing and if its a file we use at, as we check * does it end with zip extension, is missing we add it. * @param destination the possible destination archived file * @param optional an optional file to be added to the archive. * @return the resulting file in zip format */ public static File collectLogs(File destination, File optional) { if(destination == null) return null; if(!destination.isDirectory()) { if(!destination.getName().endsWith("zip")) destination = new File(destination.getParentFile(), destination.getName() + ".zip"); } else { destination = new File(destination, getDefaultFileName()); } try { ZipOutputStream out = new ZipOutputStream( new FileOutputStream(destination)); collectHomeFolderLogs(out); collectJavaCrashLogs(out); if(optional != null) { addFileToZip(optional, out); } out.close(); return destination; } catch(FileNotFoundException ex) { logger.error("Error creating logs file archive", ex); } catch(IOException ex) { logger.error("Error closing archive file", ex); } return null; } /** * The default filename to use. * @return the default filename to use. */ public static String getDefaultFileName() { return FORMAT.format(new Date()) + "-logs.zip"; } /** * Collects all files from log folder except the lock file. * And put them in the zip file as zip entries. * @param out the output zip file. */ private static void collectHomeFolderLogs(ZipOutputStream out) { try { File[] fs = LoggingUtilsActivator.getFileAccessService() .getPrivatePersistentDirectory(LOGGING_DIR_NAME, FileCategory.LOG).listFiles(); for(File f : fs) { if(f.getName().endsWith(".lck")) continue; addFileToZip(f, out); } } catch(Exception e) { logger.error("Error obtaining logs folder", e); } } /** * Copies a file to the given archive. * @param file the file to copy. * @param out the output archive stream. */ private static void addFileToZip(File file, ZipOutputStream out) { byte[] buf = new byte[1024]; try { FileInputStream in = new FileInputStream(file); // new ZIP entry out.putNextEntry(new ZipEntry( LOGGING_DIR_NAME + File.separator + file.getName())); // transfer bytes int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } out.closeEntry(); in.close(); } catch(FileNotFoundException ex) { logger.error("Error obtaining file to archive", ex); } catch(IOException ex) { logger.error("Error saving file to archive", ex); } } /** * Searches for java crash logs belonging to us and add them to * the log archive. * * @param out the output archive stream. */ private static void collectJavaCrashLogs(ZipOutputStream out) { // First check in working dir addCrashFilesToArchive( new File(".").listFiles(), JAVA_ERROR_LOG_PREFIX, out); String homeDir = System.getProperty("user.home"); // If we don't have permissions to write to working directory // the crash logs maybe in the temp directory, // or on the Desktop if its windows if(OSUtils.IS_WINDOWS) { // check Desktop File[] desktopFiles = new File(homeDir + File.separator + "Desktop").listFiles(); addCrashFilesToArchive(desktopFiles, JAVA_ERROR_LOG_PREFIX, out); } if(OSUtils.IS_MAC) { String logDir = "/Library/Logs/"; // Look in the following directories: // /Library/Logs/CrashReporter (OSX 10.4, 10.5) // ~/Library/Logs/CrashReporter (OSX 10.4, 10.5) // ~/Library/Logs/DiagnosticReports (OSX 10.6, 10.7, 10.8) // // Note that for 10.6, there are aliases in // ~/Library/Logs/CrashReporter for the crash files in // ~/Library/Logs/DiagnosticReports, but the code won't load the // aliases so we shouldn't get duplicates. String[] locations = {logDir + "CrashReporter", homeDir + logDir + "CrashReporter", homeDir + logDir + "DiagnosticReports"}; for (String location : locations) { File[] crashLogs = new File(location).listFiles(); addCrashFilesToArchive(crashLogs, null, out); } } else { // search in /tmp folder // Solaris OS and Linux the temporary directory is /tmp // windows TMP or TEMP environment variable is the temporary folder //java.io.tmpdir File[] tempFiles = new File(System.getProperty("java.io.tmpdir")).listFiles(); addCrashFilesToArchive(tempFiles, JAVA_ERROR_LOG_PREFIX, out); } } /** * Checks if file is a crash log file and does it belongs to us. * @param files files to check. * @param filterStartsWith a prefix for the files, can be null if no * prefix check should be made. * @param out the output archive stream. */ private static void addCrashFilesToArchive( File files[], String filterStartsWith, ZipOutputStream out) { // no files to add if(files == null) return; // First check in working dir for(File f: files) { if(filterStartsWith != null && !f.getName().startsWith(filterStartsWith)) { continue; } if(isOurCrashLog(f)) { addFileToZip(f, out); } } } /** * Checks whether the crash log file is for our application. * @param file the crash log file. * @return <tt>true</tt> if error log is ours. */ private static boolean isOurCrashLog(File file) { try { BufferedReader reader = new BufferedReader(new FileReader(file)); try { String line; while((line = reader.readLine()) != null) if(JAVA_ERROR_LOG_PATTERN.matcher(line).find()) return true; } finally { reader.close(); } } catch(Throwable t) {} return false; } }