| /* |
| * Copyright 1999,2004 The Apache Software Foundation. |
| * |
| * 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 org.apache.log4j.chainsaw.vfs; |
| |
| import java.awt.Container; |
| import java.awt.Dimension; |
| import java.awt.Frame; |
| import java.awt.GridBagConstraints; |
| import java.awt.GridBagLayout; |
| import java.awt.Insets; |
| import java.awt.Toolkit; |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.io.UnsupportedEncodingException; |
| |
| import javax.swing.JButton; |
| import javax.swing.JDialog; |
| import javax.swing.JLabel; |
| import javax.swing.JPanel; |
| import javax.swing.JPasswordField; |
| import javax.swing.JTextField; |
| import javax.swing.SwingUtilities; |
| |
| import org.apache.commons.vfs.FileObject; |
| import org.apache.commons.vfs.FileSystemException; |
| import org.apache.commons.vfs.FileSystemManager; |
| import org.apache.commons.vfs.FileSystemOptions; |
| import org.apache.commons.vfs.RandomAccessContent; |
| import org.apache.commons.vfs.VFS; |
| import org.apache.commons.vfs.provider.URLFileName; |
| import org.apache.commons.vfs.provider.sftp.SftpFileSystemConfigBuilder; |
| import org.apache.commons.vfs.util.RandomAccessMode; |
| import org.apache.log4j.chainsaw.receivers.VisualReceiver; |
| import org.apache.log4j.varia.LogFilePatternReceiver; |
| |
| /** |
| * A VFS-enabled version of org.apache.log4j.varia.LogFilePatternReceiver. |
| * |
| * VFSLogFilePatternReceiver can parse and tail log files, converting entries into |
| * LoggingEvents. If the file doesn't exist when the receiver is initialized, the |
| * receiver will look for the file once every 10 seconds. |
| * <p> |
| * See the Chainsaw page (http://logging.apache.org/log4j/docs/chainsaw.html) for information |
| * on how to set up Chainsaw with VFS. |
| * <p> |
| * See http://jakarta.apache.org/commons/vfs/filesystems.html for a list of VFS-supported |
| * file systems and the URIs needed to access the file systems. |
| * <p> |
| * Because some VFS file systems allow you to provide username/password, this receiver |
| * provides an optional GUI dialog for entering the username/password fields instead |
| * of requiring you to hard code usernames and passwords into the URI. |
| * <p> |
| * If the 'promptForUserInfo' param is set to true (default is false), |
| * the receiver will wait for a call to 'setContainer', and then display |
| * a username/password dialog. |
| * <p> |
| * If you are using this receiver without a GUI, don't set promptForUserInfo |
| * to true - it will block indefinitely waiting for a visual component. |
| * <p> |
| * If the 'promptForUserInfo' param is set to true, the fileURL should -leave out- |
| * the username/password portion of the VFS-supported URI. Examples: |
| * <p> |
| * An sftp URI that would be used with promptForUserInfo=true: |
| * sftp://192.168.1.100:22/home/thisuser/logfile.txt |
| * <p> |
| * An sftp URI that would be used with promptForUserInfo=false: |
| * sftp://username:password@192.168.1.100:22/home/thisuser/logfile.txt |
| * <p> |
| * This receiver relies on java.util.regex features to perform the parsing of text in the |
| * log file, however the only regular expression field explicitly supported is |
| * a glob-style wildcard used to ignore fields in the log file if needed. All other |
| * fields are parsed by using the supplied keywords. |
| * <p> |
| * <b>Features:</b><br> |
| * - specify the URL of the log file to be processed<br> |
| * - specify the timestamp format in the file (if one exists, using patterns from {@link java.text.SimpleDateFormat})<br> |
| * - specify the pattern (logFormat) used in the log file using keywords, a wildcard character (*) and fixed text<br> |
| * - 'tail' the file (allows the contents of the file to be continually read and new events processed)<br> |
| * - supports the parsing of multi-line messages and exceptions |
| * - to access |
| *<p> |
| * <b>Keywords:</b><br> |
| * TIMESTAMP<br> |
| * LOGGER<br> |
| * LEVEL<br> |
| * THREAD<br> |
| * CLASS<br> |
| * FILE<br> |
| * LINE<br> |
| * METHOD<br> |
| * RELATIVETIME<br> |
| * MESSAGE<br> |
| * NDC<br> |
| * PROP(key)<br> |
| * <p> |
| * Use a * to ignore portions of the log format that should be ignored |
| * <p> |
| * Example:<br> |
| * If your file's patternlayout is this:<br> |
| * <b>%d %-5p [%t] %C{2} (%F:%L) - %m%n</b> |
| *<p> |
| * specify this as the log format:<br> |
| * <b>TIMESTAMP LEVEL [THREAD] CLASS (FILE:LINE) - MESSAGE</b> |
| *<p> |
| * To define a PROPERTY field, use PROP(key) |
| * <p> |
| * Example:<br> |
| * If you used the RELATIVETIME pattern layout character in the file, |
| * you can use PROP(RELATIVETIME) in the logFormat definition to assign |
| * the RELATIVETIME field as a property on the event. |
| * <p> |
| * If your file's patternlayout is this:<br> |
| * <b>%r [%t] %-5p %c %x - %m%n</b> |
| *<p> |
| * specify this as the log format:<br> |
| * <b>PROP(RELATIVETIME) [THREAD] LEVEL LOGGER * - MESSAGE</b> |
| * <p> |
| * Note the * - it can be used to ignore a single word or sequence of words in the log file |
| * (in order for the wildcard to ignore a sequence of words, the text being ignored must be |
| * followed by some delimiter, like '-' or '[') - ndc is being ignored in this example. |
| * <p> |
| * Assign a filterExpression in order to only process events which match a filter. |
| * If a filterExpression is not assigned, all events are processed. |
| *<p> |
| * <b>Limitations:</b><br> |
| * - no support for the single-line version of throwable supported by patternlayout<br> |
| * (this version of throwable will be included as the last line of the message)<br> |
| * - the relativetime patternLayout character must be set as a property: PROP(RELATIVETIME)<br> |
| * - messages should appear as the last field of the logFormat because the variability in message content<br> |
| * - exceptions are converted if the exception stack trace (other than the first line of the exception)<br> |
| * is stored in the log file with a tab followed by the word 'at' as the first characters in the line<br> |
| * - tailing may fail if the file rolls over. |
| *<p> |
| * <b>Example receiver configuration settings</b> (add these as params, specifying a LogFilePatternReceiver 'plugin'):<br> |
| * param: "timestampFormat" value="yyyy-MM-d HH:mm:ss,SSS"<br> |
| * param: "logFormat" value="RELATIVETIME [THREAD] LEVEL LOGGER * - MESSAGE"<br> |
| * param: "fileURL" value="file:///c:/events.log"<br> |
| * param: "tailing" value="true" |
| * param: "promptForUserInfo" value="false" |
| *<p> |
| * This configuration will be able to process these sample events:<br> |
| * 710 [ Thread-0] DEBUG first.logger first - <test> <test2>something here</test2> <test3 blah=something/> <test4> <test5>something else</test5> </test4></test><br> |
| * 880 [ Thread-2] DEBUG first.logger third - <test> <test2>something here</test2> <test3 blah=something/> <test4> <test5>something else</test5> </test4></test><br> |
| * 880 [ Thread-0] INFO first.logger first - infomsg-0<br> |
| * java.lang.Exception: someexception-first<br> |
| * at Generator2.run(Generator2.java:102)<br> |
| * |
| *@author Scott Deboy |
| */ |
| public class VFSLogFilePatternReceiver extends LogFilePatternReceiver implements VisualReceiver { |
| |
| private boolean promptForUserInfo = false; |
| private Container container; |
| private final Object waitForContainerLock = new Object(); |
| private boolean autoReconnect; |
| private VFSReader vfsReader; |
| |
| public VFSLogFilePatternReceiver() { |
| super(); |
| } |
| |
| public void shutdown() { |
| getLogger().info("shutdown VFSLogFilePatternReceiver"); |
| active = false; |
| container = null; |
| if (vfsReader != null) { |
| vfsReader.terminate(); |
| vfsReader = null; |
| } |
| } |
| |
| /** |
| * If set to true, will cause the receiver to block indefinitely until 'setContainer' has been called, |
| * at which point a username/password dialog will appear. |
| * |
| * @param promptForUserInfo |
| */ |
| public void setPromptForUserInfo(boolean promptForUserInfo) { |
| this.promptForUserInfo = promptForUserInfo; |
| } |
| |
| public boolean isPromptForUserInfo() { |
| return promptForUserInfo; |
| } |
| |
| /** |
| * Accessor |
| * @return |
| */ |
| public boolean isAutoReconnect() { |
| return autoReconnect; |
| } |
| |
| /** |
| * Mutator |
| * @param autoReconnect |
| */ |
| public void setAutoReconnect(boolean autoReconnect) { |
| this.autoReconnect = autoReconnect; |
| } |
| |
| /** |
| * Implementation of VisualReceiver interface - allows this receiver to provide |
| * a username/password dialog. |
| */ |
| public void setContainer(Container container) { |
| if (promptForUserInfo) { |
| synchronized(waitForContainerLock) { |
| this.container=container; |
| waitForContainerLock.notify(); |
| } |
| } |
| } |
| |
| /** |
| * Read and process the log file. |
| */ |
| public void activateOptions() { |
| //we don't want to call super.activateOptions, but we do want active to be set to true |
| active = true; |
| //on receiver restart, only prompt for credentials if we don't already have them |
| if (promptForUserInfo && !getFileURL().contains("@")) { |
| /* |
| if promptforuserinfo is true, wait for a reference to the container |
| (via the VisualReceiver callback). |
| |
| We need to display a login dialog on top of the container, so we must then |
| wait until the container has been added to a frame |
| */ |
| |
| //get a reference to the container |
| new Thread(() -> { |
| synchronized(waitForContainerLock) { |
| while (container == null) { |
| try { |
| waitForContainerLock.wait(1000); |
| getLogger().debug("waiting for setContainer call"); |
| } catch (InterruptedException ie){} |
| } |
| } |
| |
| Frame containerFrame1; |
| if (container instanceof Frame) { |
| containerFrame1 = (Frame)container; |
| } else { |
| synchronized(waitForContainerLock) { |
| //loop until the container has a frame |
| while ((containerFrame1 = (Frame)SwingUtilities.getAncestorOfClass(Frame.class, container)) == null) { |
| try { |
| waitForContainerLock.wait(1000); |
| getLogger().debug("waiting for container's frame to be available"); |
| } catch (InterruptedException ie) {} |
| } |
| } |
| } |
| final Frame containerFrame = containerFrame1; |
| //create the dialog |
| SwingUtilities.invokeLater(() -> { |
| Frame owner = null; |
| if (container != null) { |
| owner = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, containerFrame); |
| } |
| final UserNamePasswordDialog f = new UserNamePasswordDialog(owner); |
| f.pack(); |
| Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); |
| f.setLocation(d.width / 2, d.height / 2); |
| f.setVisible(true); |
| if (null == f.getUserName() || null == f.getPassword()) { |
| getLogger().info("Username and password not both provided, not using credentials"); |
| } else { |
| String oldURL = getFileURL(); |
| int index = oldURL.indexOf("://"); |
| String firstPart = oldURL.substring(0, index); |
| String lastPart = oldURL.substring(index + "://".length()); |
| setFileURL(firstPart + "://" + f.getUserName() + ":" + new String(f.getPassword()) + "@" + lastPart); |
| |
| setHost(oldURL.substring(0, index + "://".length())); |
| setPath(oldURL.substring(index + "://".length())); |
| } |
| vfsReader = new VFSReader(); |
| new Thread(vfsReader).start(); |
| }); |
| }).start(); |
| } else { |
| //starts with protocol:/ but not protocol:// |
| String oldURL = getFileURL(); |
| if (oldURL != null && oldURL.contains(":/") && !oldURL.contains("://")) { |
| int index = oldURL.indexOf(":/"); |
| String lastPart = oldURL.substring(index + ":/".length()); |
| int passEndIndex = lastPart.indexOf("@"); |
| if (passEndIndex > -1) { //we have a username/password |
| setHost(oldURL.substring(0, index + ":/".length())); |
| setPath(lastPart.substring(passEndIndex + 1)); |
| } |
| vfsReader = new VFSReader(); |
| new Thread(vfsReader).start(); |
| } else if (oldURL != null && oldURL.contains("://")) { |
| //starts with protocol:// |
| int index = oldURL.indexOf("://"); |
| String lastPart = oldURL.substring(index + "://".length()); |
| int passEndIndex = lastPart.indexOf("@"); |
| if (passEndIndex > -1) { //we have a username/password |
| setHost(oldURL.substring(0, index + "://".length())); |
| setPath(lastPart.substring(passEndIndex + 1)); |
| } |
| vfsReader = new VFSReader(); |
| new Thread(vfsReader).start(); |
| } else { |
| getLogger().info("null URL - unable to parse file"); |
| } |
| } |
| } |
| |
| private class VFSReader implements Runnable { |
| private boolean terminated = false; |
| private Reader reader; |
| private FileObject fileObject; |
| |
| public void run() { |
| //thread should end when we're no longer active |
| while (reader == null && !terminated) { |
| int atIndex = getFileURL().indexOf("@"); |
| int protocolIndex = getFileURL().indexOf("://"); |
| |
| String loggableFileURL = atIndex > -1? getFileURL().substring(0, protocolIndex + "://".length()) + "username:password" + getFileURL().substring(atIndex) : getFileURL(); |
| getLogger().info("attempting to load file: " + loggableFileURL); |
| try { |
| FileSystemManager fileSystemManager = VFS.getManager(); |
| FileSystemOptions opts = new FileSystemOptions(); |
| //if jsch not in classpath, can get NoClassDefFoundError here |
| try { |
| SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(opts, "no"); |
| } catch (NoClassDefFoundError ncdfe) { |
| getLogger().warn("JSch not on classpath!", ncdfe); |
| } |
| |
| synchronized(fileSystemManager) { |
| fileObject = fileSystemManager.resolveFile(getFileURL(), opts); |
| if (fileObject.exists()) { |
| reader = new InputStreamReader(fileObject.getContent().getInputStream() , "UTF-8"); |
| //now that we have a reader, remove additional portions of the file url (sftp passwords, etc.) |
| //check to see if the name is a URLFileName..if so, set file name to not include username/pass |
| if (fileObject.getName() instanceof URLFileName) { |
| URLFileName urlFileName = (URLFileName) fileObject.getName(); |
| setHost(urlFileName.getHostName()); |
| setPath(urlFileName.getPath()); |
| } |
| } else { |
| getLogger().info(loggableFileURL + " not available - will re-attempt to load after waiting " + MISSING_FILE_RETRY_MILLIS + " millis"); |
| } |
| } |
| } catch (FileSystemException fse) { |
| getLogger().info(loggableFileURL + " not available - may be due to incorrect credentials, but will re-attempt to load after waiting " + MISSING_FILE_RETRY_MILLIS + " millis", fse); |
| } catch (UnsupportedEncodingException e) { |
| getLogger().info("UTF-8 not available", e); |
| } |
| if (reader == null) { |
| synchronized (this) { |
| try { |
| wait(MISSING_FILE_RETRY_MILLIS); |
| } catch (InterruptedException ie) {} |
| } |
| } |
| } |
| if (terminated) { |
| //shut down while waiting for a file |
| return; |
| } |
| initialize(); |
| getLogger().debug(getPath() + " exists"); |
| |
| do { |
| long lastFilePointer = 0; |
| long lastFileSize = 0; |
| createPattern(); |
| try { |
| do { |
| FileSystemManager fileSystemManager = VFS.getManager(); |
| FileSystemOptions opts = new FileSystemOptions(); |
| //if jsch not in classpath, can get NoClassDefFoundError here |
| try { |
| SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(opts, "no"); |
| } catch (NoClassDefFoundError ncdfe) { |
| getLogger().warn("JSch not on classpath!", ncdfe); |
| } |
| |
| //fileobject was created above, release it and construct a new one |
| synchronized(fileSystemManager) { |
| if (fileObject != null) { |
| fileObject.getFileSystem().getFileSystemManager().closeFileSystem(fileObject.getFileSystem()); |
| fileObject.close(); |
| fileObject = null; |
| } |
| |
| fileObject = fileSystemManager.resolveFile(getFileURL(), opts); |
| } |
| |
| //file may not exist.. |
| boolean fileLarger = false; |
| if (fileObject != null && fileObject.exists()) { |
| try { |
| //available in vfs as of 30 Mar 2006 - will load but not tail if not available |
| fileObject.refresh(); |
| } catch (Error err) { |
| getLogger().info(getPath() + " - unable to refresh fileobject", err); |
| } |
| //could have been truncated or appended to (don't do anything if same size) |
| if (fileObject.getContent().getSize() < lastFileSize) { |
| reader = new InputStreamReader(fileObject.getContent().getInputStream(), "UTF-8"); |
| getLogger().debug(getPath() + " was truncated"); |
| lastFileSize = 0; //seek to beginning of file |
| lastFilePointer = 0; |
| } else if (fileObject.getContent().getSize() > lastFileSize) { |
| fileLarger = true; |
| RandomAccessContent rac = fileObject.getContent().getRandomAccessContent(RandomAccessMode.READ); |
| rac.seek(lastFilePointer); |
| reader = new InputStreamReader(rac.getInputStream(), "UTF-8"); |
| BufferedReader bufferedReader = new BufferedReader(reader); |
| process(bufferedReader); |
| lastFilePointer = rac.getFilePointer(); |
| lastFileSize = fileObject.getContent().getSize(); |
| rac.close(); |
| } |
| try { |
| if (reader != null) { |
| reader.close(); |
| reader = null; |
| } |
| } catch (IOException ioe) { |
| getLogger().debug(getPath() + " - unable to close reader", ioe); |
| } |
| } else { |
| getLogger().info(getPath() + " - not available - will re-attempt to load after waiting " + getWaitMillis() + " millis"); |
| } |
| |
| try { |
| synchronized (this) { |
| wait(getWaitMillis()); |
| } |
| } catch (InterruptedException ie) {} |
| if (isTailing() && fileLarger && !terminated) { |
| getLogger().debug(getPath() + " - tailing file - file size: " + lastFileSize); |
| } |
| } while (isTailing() && !terminated); |
| } catch (IOException ioe) { |
| getLogger().info(getPath() + " - exception processing file", ioe); |
| try { |
| if (fileObject != null) { |
| fileObject.close(); |
| } |
| } catch (FileSystemException e) { |
| getLogger().info(getPath() + " - exception processing file", e); |
| } |
| try { |
| synchronized(this) { |
| wait(getWaitMillis()); |
| } |
| } catch (InterruptedException ie) {} |
| } |
| } while (isAutoReconnect() && !terminated); |
| getLogger().debug(getPath() + " - processing complete"); |
| } |
| |
| public void terminate() |
| { |
| terminated = true; |
| } |
| } |
| |
| public class UserNamePasswordDialog extends JDialog { |
| private String userName; |
| private char[] password; |
| private UserNamePasswordDialog(Frame containerFrame) { |
| super(containerFrame, "Login", true); |
| JPanel panel = new JPanel(new GridBagLayout()); |
| GridBagConstraints gc = new GridBagConstraints(); |
| gc.fill=GridBagConstraints.NONE; |
| |
| gc.anchor=GridBagConstraints.NORTH; |
| gc.gridx=0; |
| gc.gridy=0; |
| gc.gridwidth=3; |
| gc.insets=new Insets(7, 7, 7, 7); |
| panel.add(new JLabel("URI: " + getFileURL()), gc); |
| |
| gc.gridx=0; |
| gc.gridy=1; |
| gc.gridwidth=1; |
| gc.insets=new Insets(2, 2, 2, 2); |
| panel.add(new JLabel("Username"), gc); |
| |
| gc.gridx=1; |
| gc.gridy=1; |
| gc.gridwidth=2; |
| gc.weightx=1.0; |
| gc.fill=GridBagConstraints.HORIZONTAL; |
| |
| final JTextField userNameTextField = new JTextField(15); |
| panel.add(userNameTextField, gc); |
| |
| gc.gridx=0; |
| gc.gridy=2; |
| gc.gridwidth=1; |
| gc.fill=GridBagConstraints.NONE; |
| |
| panel.add(new JLabel("Password"), gc); |
| |
| gc.gridx=1; |
| gc.gridy=2; |
| gc.gridwidth=2; |
| gc.fill=GridBagConstraints.HORIZONTAL; |
| |
| final JPasswordField passwordTextField = new JPasswordField(15); |
| panel.add(passwordTextField, gc); |
| |
| gc.gridy=3; |
| gc.anchor=GridBagConstraints.SOUTH; |
| gc.fill=GridBagConstraints.NONE; |
| |
| JButton submitButton = new JButton(" Submit "); |
| panel.add(submitButton, gc); |
| |
| getContentPane().add(panel); |
| submitButton.addActionListener(evt -> { |
| userName = userNameTextField.getText(); |
| password = passwordTextField.getPassword(); |
| getContentPane().setVisible(false); |
| UserNamePasswordDialog.this.dispose(); |
| }); |
| } |
| |
| public String getUserName() { |
| return userName; |
| } |
| |
| public char[] getPassword() { |
| return password; |
| } |
| } |
| } |