001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.log4j.varia;
019
020import java.io.DataInputStream;
021import java.io.DataOutputStream;
022import java.io.IOException;
023import java.io.InterruptedIOException;
024import java.net.ServerSocket;
025import java.net.Socket;
026
027import org.apache.log4j.RollingFileAppender;
028import org.apache.log4j.helpers.LogLog;
029
030/**
031   This appender listens on a socket on the port specified by the
032   <b>Port</b> property for a "RollOver" message. When such a message
033   is received, the underlying log file is rolled over and an
034   acknowledgment message is sent back to the process initiating the
035   roll over.
036
037   <p>This method of triggering roll over has the advantage of being
038   operating system independent, fast and reliable.
039
040   <p>A simple application {@link Roller} is provided to initiate the
041   roll over.
042
043   <p>Note that the initiator is not authenticated. Anyone can trigger
044   a rollover. In production environments, it is recommended that you
045   add some form of protection to prevent undesired rollovers.
046
047
048   @author Ceki G&uuml;lc&uuml;
049   @since version 0.9.0 */
050public class ExternallyRolledFileAppender extends RollingFileAppender {
051
052  /**
053     The string constant sent to initiate a roll over.   Current value of
054     this string constant is <b>RollOver</b>.
055  */
056  static final public String ROLL_OVER = "RollOver";
057
058  /**
059     The string constant sent to acknowledge a roll over.   Current value of
060      this string constant is <b>OK</b>.
061  */
062  static final public String OK = "OK";
063
064  int port = 0;
065  HUP hup;
066
067  /**
068     The default constructor does nothing but calls its super-class
069     constructor.  */
070  public
071  ExternallyRolledFileAppender() {
072  }
073
074  /**
075     The <b>Port</b> [roperty is used for setting the port for
076     listening to external roll over messages.
077  */
078  public
079  void setPort(int port) {
080    this.port = port;
081  }
082
083  /**
084     Returns value of the <b>Port</b> option.
085   */
086  public
087  int getPort() {
088    return port;
089  }
090
091  /**
092     Start listening on the port specified by a preceding call to
093     {@link #setPort}.  */
094  public
095  void activateOptions() {
096    super.activateOptions();
097    if(port != 0) {
098      if(hup != null) {
099        hup.interrupt();
100      }
101      hup = new HUP(this, port);
102      hup.setDaemon(true);
103      hup.start();
104    }
105  }
106}
107
108
109class HUP extends Thread {
110
111  int port;
112  ExternallyRolledFileAppender er;
113
114  HUP(ExternallyRolledFileAppender er, int port) {
115    this.er = er;
116    this.port = port;
117  }
118
119  public
120  void run() {
121    while(!isInterrupted()) {
122      try {
123        ServerSocket serverSocket = new ServerSocket(port);
124        while(true) {
125          Socket socket = serverSocket.accept();
126          LogLog.debug("Connected to client at " + socket.getInetAddress());
127          new Thread(new HUPNode(socket, er), "ExternallyRolledFileAppender-HUP").start();
128        }
129      } catch(InterruptedIOException e) {
130        Thread.currentThread().interrupt();
131            e.printStackTrace();
132      } catch(IOException e) {
133            e.printStackTrace();
134      } catch(RuntimeException e) {
135            e.printStackTrace();
136      }
137    }
138  }
139}
140
141class HUPNode implements Runnable {
142
143  Socket socket;
144  DataInputStream dis;
145  DataOutputStream dos;
146  ExternallyRolledFileAppender er;
147
148  public
149  HUPNode(Socket socket, ExternallyRolledFileAppender er) {
150    this.socket = socket;
151    this.er = er;
152    try {
153      dis = new DataInputStream(socket.getInputStream());
154      dos = new DataOutputStream(socket.getOutputStream());
155    } catch(InterruptedIOException e) {
156      Thread.currentThread().interrupt();
157      e.printStackTrace();
158    } catch(IOException e) {
159      e.printStackTrace();
160    } catch(RuntimeException e) {
161      e.printStackTrace();
162    }
163  }
164
165  public void run() {
166    try {
167      String line = dis.readUTF();
168      LogLog.debug("Got external roll over signal.");
169      if(ExternallyRolledFileAppender.ROLL_OVER.equals(line)) {
170        synchronized(er) {
171          er.rollOver();
172        }
173        dos.writeUTF(ExternallyRolledFileAppender.OK);
174      }
175      else {
176        dos.writeUTF("Expecting [RollOver] string.");
177      }
178      dos.close();
179    } catch(InterruptedIOException e) {
180      Thread.currentThread().interrupt();
181      LogLog.error("Unexpected exception. Exiting HUPNode.", e);
182    } catch(IOException e) {
183      LogLog.error("Unexpected exception. Exiting HUPNode.", e);
184    } catch(RuntimeException e) {
185      LogLog.error("Unexpected exception. Exiting HUPNode.", e);
186    }
187  }
188}
189