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.net;
019
020import java.io.File;
021import java.net.InetAddress;
022import java.net.ServerSocket;
023import java.net.Socket;
024import java.util.Hashtable;
025
026import org.apache.log4j.Hierarchy;
027import org.apache.log4j.Level;
028import org.apache.log4j.LogManager;
029import org.apache.log4j.Logger;
030import org.apache.log4j.PropertyConfigurator;
031import org.apache.log4j.spi.LoggerRepository;
032import org.apache.log4j.spi.RootLogger;
033
034
035/**
036   A {@link SocketNode} based server that uses a different hierarchy
037   for each client.
038
039   <pre>
040     <b>Usage:</b> java org.apache.log4j.net.SocketServer port configFile configDir
041
042     where <b>port</b> is a part number where the server listens,
043           <b>configFile</b> is a configuration file fed to the {@link PropertyConfigurator} and
044           <b>configDir</b> is a path to a directory containing configuration files, possibly one for each client host.
045     </pre>
046
047     <p>The <code>configFile</code> is used to configure the log4j
048     default hierarchy that the <code>SocketServer</code> will use to
049     report on its actions.
050
051     <p>When a new connection is opened from a previously unknown
052     host, say <code>foo.bar.net</code>, then the
053     <code>SocketServer</code> will search for a configuration file
054     called <code>foo.bar.net.lcf</code> under the directory
055     <code>configDir</code> that was passed as the third argument. If
056     the file can be found, then a new hierarchy is instantiated and
057     configured using the configuration file
058     <code>foo.bar.net.lcf</code>. If and when the host
059     <code>foo.bar.net</code> opens another connection to the server,
060     then the previously configured hierarchy is used.
061
062     <p>In case there is no file called <code>foo.bar.net.lcf</code>
063     under the directory <code>configDir</code>, then the
064     <em>generic</em> hierarchy is used. The generic hierarchy is
065     configured using a configuration file called
066     <code>generic.lcf</code> under the <code>configDir</code>
067     directory. If no such file exists, then the generic hierarchy will be
068     identical to the log4j default hierarchy.
069
070     <p>Having different client hosts log using different hierarchies
071     ensures the total independence of the clients with respect to
072     their logging settings.
073
074     <p>Currently, the hierarchy that will be used for a given request
075     depends on the IP address of the client host. For example, two
076     separate applicatons running on the same host and logging to the
077     same server will share the same hierarchy. This is perfectly safe
078     except that it might not provide the right amount of independence
079     between applications. The <code>SocketServer</code> is intended
080     as an example to be enhanced in order to implement more elaborate
081     policies.
082
083
084    @author  Ceki G&uuml;lc&uuml;
085
086    @since 1.0 */
087
088public class SocketServer  {
089
090  static String GENERIC = "generic";
091  static String CONFIG_FILE_EXT = ".lcf";
092
093  static Logger cat = Logger.getLogger(SocketServer.class);
094  static SocketServer server;
095  static int port;
096
097  // key=inetAddress, value=hierarchy
098  Hashtable hierarchyMap;
099  LoggerRepository genericHierarchy;
100  File dir;
101
102  public
103  static
104  void main(String argv[]) {
105    if(argv.length == 3)
106      init(argv[0], argv[1], argv[2]);
107    else
108      usage("Wrong number of arguments.");
109
110    try {
111      cat.info("Listening on port " + port);
112      ServerSocket serverSocket = new ServerSocket(port);
113      while(true) {
114        cat.info("Waiting to accept a new client.");
115        Socket socket = serverSocket.accept();
116        InetAddress inetAddress =  socket.getInetAddress();
117        cat.info("Connected to client at " + inetAddress);
118
119        LoggerRepository h = (LoggerRepository) server.hierarchyMap.get(inetAddress);
120        if(h == null) {
121          h = server.configureHierarchy(inetAddress);
122        }
123
124        cat.info("Starting new socket node.");
125        new Thread(new SocketNode(socket, h)).start();
126      }
127    }
128    catch(Exception e) {
129      e.printStackTrace();
130    }
131  }
132
133
134  static
135  void  usage(String msg) {
136    System.err.println(msg);
137    System.err.println(
138      "Usage: java " +SocketServer.class.getName() + " port configFile directory");
139    System.exit(1);
140  }
141
142  static
143  void init(String portStr, String configFile, String dirStr) {
144    try {
145      port = Integer.parseInt(portStr);
146    }
147    catch(java.lang.NumberFormatException e) {
148      e.printStackTrace();
149      usage("Could not interpret port number ["+ portStr +"].");
150    }
151
152    PropertyConfigurator.configure(configFile);
153
154    File dir = new File(dirStr);
155    if(!dir.isDirectory()) {
156      usage("["+dirStr+"] is not a directory.");
157    }
158    server = new SocketServer(dir);
159  }
160
161
162  public
163  SocketServer(File directory) {
164    this.dir = directory;
165    hierarchyMap = new Hashtable(11);
166  }
167
168  // This method assumes that there is no hiearchy for inetAddress
169  // yet. It will configure one and return it.
170  LoggerRepository configureHierarchy(InetAddress inetAddress) {
171    cat.info("Locating configuration file for "+inetAddress);
172    // We assume that the toSting method of InetAddress returns is in
173    // the format hostname/d1.d2.d3.d4 e.g. torino/192.168.1.1
174    String s = inetAddress.toString();
175    int i = s.indexOf("/");
176    if(i == -1) {
177      cat.warn("Could not parse the inetAddress ["+inetAddress+
178               "]. Using default hierarchy.");
179      return genericHierarchy();
180    } else {
181      String key = s.substring(0, i);
182
183      File configFile = new File(dir, key+CONFIG_FILE_EXT);
184      if(configFile.exists()) {
185        Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));
186        hierarchyMap.put(inetAddress, h);
187
188        new PropertyConfigurator().doConfigure(configFile.getAbsolutePath(), h);
189
190        return h;
191      } else {
192        cat.warn("Could not find config file ["+configFile+"].");
193        return genericHierarchy();
194      }
195    }
196  }
197
198  LoggerRepository  genericHierarchy() {
199    if(genericHierarchy == null) {
200      File f = new File(dir, GENERIC+CONFIG_FILE_EXT);
201      if(f.exists()) {
202        genericHierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
203        new PropertyConfigurator().doConfigure(f.getAbsolutePath(), genericHierarchy);
204      } else {
205        cat.warn("Could not find config file ["+f+
206                 "]. Will use the default hierarchy.");
207        genericHierarchy = LogManager.getLoggerRepository();
208      }
209    }
210    return genericHierarchy;
211  }
212}