View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.http.log;
19  
20  import com.google.common.base.Charsets;
21  
22  import java.io.BufferedReader;
23  import java.io.IOException;
24  import java.io.InputStreamReader;
25  import java.io.PrintWriter;
26  import java.net.URL;
27  import java.net.URLConnection;
28  import java.util.regex.Pattern;
29  
30  import javax.net.ssl.HttpsURLConnection;
31  import javax.net.ssl.SSLSocketFactory;
32  import javax.servlet.ServletException;
33  import javax.servlet.http.HttpServlet;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.commons.logging.impl.Jdk14Logger;
40  import org.apache.commons.logging.impl.Log4JLogger;
41  import org.apache.hadoop.HadoopIllegalArgumentException;
42  import org.apache.hadoop.conf.Configuration;
43  import org.apache.hadoop.conf.Configured;
44  import org.apache.hadoop.hbase.classification.InterfaceAudience;
45  import org.apache.hadoop.hbase.classification.InterfaceStability;
46  import org.apache.hadoop.hbase.http.HttpServer;
47  import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
48  import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
49  import org.apache.hadoop.security.ssl.SSLFactory;
50  import org.apache.hadoop.util.ServletUtil;
51  import org.apache.hadoop.util.Tool;
52  
53  /**
54   * Change log level in runtime.
55   */
56  @InterfaceStability.Evolving
57  public class LogLevel {
58    private static final String USAGES = "\nUsage: General options are:\n"
59        + "\t[-getlevel <host:port> <classname> [-protocol (http|https)]\n"
60        + "\t[-setlevel <host:port> <classname> <level> [-protocol (http|https)]";
61  
62    public static final String PROTOCOL_HTTP = "http";
63    public static final String PROTOCOL_HTTPS = "https";
64  
65    /**
66     * A command line implementation
67     */
68    public static void main(String[] args) throws Exception {
69      CLI cli = new CLI(new Configuration());
70      System.exit(cli.run(args));
71    }
72  
73    /**
74     * Valid command line options.
75     */
76    private enum Operations {
77      GETLEVEL,
78      SETLEVEL,
79      UNKNOWN
80    }
81  
82    private static void printUsage() {
83      System.err.println(USAGES);
84      System.exit(-1);
85    }
86  
87    public static boolean isValidProtocol(String protocol) {
88      return ((protocol.equals(PROTOCOL_HTTP) || protocol.equals(PROTOCOL_HTTPS)));
89    }
90  
91    @InterfaceAudience.Private
92    static class CLI extends Configured implements Tool {
93      private Operations operation = Operations.UNKNOWN;
94      private String protocol;
95      private String hostName;
96      private String className;
97      private String level;
98  
99      CLI(Configuration conf) {
100       setConf(conf);
101     }
102 
103     @Override
104     public int run(String[] args) throws Exception {
105       try {
106         parseArguments(args);
107         sendLogLevelRequest();
108       } catch (HadoopIllegalArgumentException e) {
109         printUsage();
110       }
111       return 0;
112     }
113 
114     /**
115      * Send HTTP request to the daemon.
116      * @throws HadoopIllegalArgumentException if arguments are invalid.
117      * @throws Exception if unable to connect
118      */
119     private void sendLogLevelRequest()
120         throws HadoopIllegalArgumentException, Exception {
121       switch (operation) {
122         case GETLEVEL:
123           doGetLevel();
124           break;
125         case SETLEVEL:
126           doSetLevel();
127           break;
128         default:
129           throw new HadoopIllegalArgumentException(
130               "Expect either -getlevel or -setlevel");
131       }
132     }
133 
134     public void parseArguments(String[] args) throws
135         HadoopIllegalArgumentException {
136       if (args.length == 0) {
137         throw new HadoopIllegalArgumentException("No arguments specified");
138       }
139       int nextArgIndex = 0;
140       while (nextArgIndex < args.length) {
141         switch (args[nextArgIndex]) {
142           case "-getlevel":
143             nextArgIndex = parseGetLevelArgs(args, nextArgIndex);
144             break;
145           case "-setlevel":
146             nextArgIndex = parseSetLevelArgs(args, nextArgIndex);
147             break;
148           case "-protocol":
149             nextArgIndex = parseProtocolArgs(args, nextArgIndex);
150             break;
151           default:
152             throw new HadoopIllegalArgumentException(
153                 "Unexpected argument " + args[nextArgIndex]);
154         }
155       }
156 
157       // if operation is never specified in the arguments
158       if (operation == Operations.UNKNOWN) {
159         throw new HadoopIllegalArgumentException(
160             "Must specify either -getlevel or -setlevel");
161       }
162 
163       // if protocol is unspecified, set it as http.
164       if (protocol == null) {
165         protocol = PROTOCOL_HTTP;
166       }
167     }
168 
169     private int parseGetLevelArgs(String[] args, int index) throws
170         HadoopIllegalArgumentException {
171       // fail if multiple operations are specified in the arguments
172       if (operation != Operations.UNKNOWN) {
173         throw new HadoopIllegalArgumentException("Redundant -getlevel command");
174       }
175       // check number of arguments is sufficient
176       if (index + 2 >= args.length) {
177         throw new HadoopIllegalArgumentException("-getlevel needs two parameters");
178       }
179       operation = Operations.GETLEVEL;
180       hostName = args[index + 1];
181       className = args[index + 2];
182       return index + 3;
183     }
184 
185     private int parseSetLevelArgs(String[] args, int index) throws
186         HadoopIllegalArgumentException {
187       // fail if multiple operations are specified in the arguments
188       if (operation != Operations.UNKNOWN) {
189         throw new HadoopIllegalArgumentException("Redundant -setlevel command");
190       }
191       // check number of arguments is sufficient
192       if (index + 3 >= args.length) {
193         throw new HadoopIllegalArgumentException("-setlevel needs three parameters");
194       }
195       operation = Operations.SETLEVEL;
196       hostName = args[index + 1];
197       className = args[index + 2];
198       level = args[index + 3];
199       return index + 4;
200     }
201 
202     private int parseProtocolArgs(String[] args, int index) throws
203         HadoopIllegalArgumentException {
204       // make sure only -protocol is specified
205       if (protocol != null) {
206         throw new HadoopIllegalArgumentException("Redundant -protocol command");
207       }
208       // check number of arguments is sufficient
209       if (index + 1 >= args.length) {
210         throw new HadoopIllegalArgumentException("-protocol needs one parameter");
211       }
212       // check protocol is valid
213       protocol = args[index + 1];
214       if (!isValidProtocol(protocol)) {
215         throw new HadoopIllegalArgumentException("Invalid protocol: " + protocol);
216       }
217       return index + 2;
218     }
219 
220     /**
221      * Send HTTP request to get log level.
222      *
223      * @throws HadoopIllegalArgumentException if arguments are invalid.
224      * @throws Exception if unable to connect
225      */
226     private void doGetLevel() throws Exception {
227       process(protocol + "://" + hostName + "/logLevel?log=" + className);
228     }
229 
230     /**
231      * Send HTTP request to set log level.
232      *
233      * @throws HadoopIllegalArgumentException if arguments are invalid.
234      * @throws Exception if unable to connect
235      */
236     private void doSetLevel() throws Exception {
237       process(protocol + "://" + hostName + "/logLevel?log=" + className
238           + "&level=" + level);
239     }
240 
241     /**
242      * Connect to the URL. Supports HTTP and supports SPNEGO
243      * authentication. It falls back to simple authentication if it fails to
244      * initiate SPNEGO.
245      *
246      * @param url the URL address of the daemon servlet
247      * @return a connected connection
248      * @throws Exception if it can not establish a connection.
249      */
250     private URLConnection connect(URL url) throws Exception {
251       AuthenticatedURL.Token token = new AuthenticatedURL.Token();
252       AuthenticatedURL aUrl;
253       SSLFactory clientSslFactory;
254       URLConnection connection;
255 
256       // If https is chosen, configures SSL client.
257       if (PROTOCOL_HTTPS.equals(url.getProtocol())) {
258         clientSslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, this.getConf());
259         clientSslFactory.init();
260         SSLSocketFactory sslSocketF = clientSslFactory.createSSLSocketFactory();
261 
262         aUrl = new AuthenticatedURL(new KerberosAuthenticator(), clientSslFactory);
263         connection = aUrl.openConnection(url, token);
264         HttpsURLConnection httpsConn = (HttpsURLConnection) connection;
265         httpsConn.setSSLSocketFactory(sslSocketF);
266       } else {
267         aUrl = new AuthenticatedURL(new KerberosAuthenticator());
268         connection = aUrl.openConnection(url, token);
269       }
270       connection.connect();
271       return connection;
272     }
273 
274     /**
275      * Configures the client to send HTTP request to the URL.
276      * Supports SPENGO for authentication.
277      * @param urlString URL and query string to the daemon's web UI
278      * @throws Exception if unable to connect
279      */
280     private void process(String urlString) throws Exception {
281       URL url = new URL(urlString);
282       System.out.println("Connecting to " + url);
283 
284       URLConnection connection = connect(url);
285 
286       // read from the servlet
287 
288       try (InputStreamReader streamReader =
289           new InputStreamReader(connection.getInputStream(), Charsets.UTF_8);
290           BufferedReader bufferedReader = new BufferedReader(streamReader)) {
291         String line;
292         while((line = bufferedReader.readLine()) != null) {
293           if (line.startsWith(MARKER)) {
294             System.out.println(TAG.matcher(line).replaceAll(""));
295           }
296         }
297       } catch (IOException ioe) {
298         System.err.println("" + ioe);
299       }
300     }
301   }
302 
303   private static final String MARKER = "<!-- OUTPUT -->";
304   private static final Pattern TAG = Pattern.compile("<[^>]*>");
305 
306   /**
307    * A servlet implementation
308    */
309   @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
310   @InterfaceStability.Unstable
311   public static class Servlet extends HttpServlet {
312     private static final long serialVersionUID = 1L;
313 
314     @Override
315     public void doGet(HttpServletRequest request, HttpServletResponse response)
316         throws ServletException, IOException {
317 
318       // Do the authorization
319       if (!HttpServer.hasAdministratorAccess(getServletContext(), request,
320           response)) {
321         return;
322       }
323 
324       PrintWriter out = ServletUtil.initHTML(response, "Log Level");
325       String logName = ServletUtil.getParameter(request, "log");
326       String level = ServletUtil.getParameter(request, "level");
327 
328       if (logName != null) {
329         out.println("<br /><hr /><h3>Results</h3>");
330         out.println(MARKER
331             + "Submitted Log Name: <b>" + logName + "</b><br />");
332 
333         Log log = LogFactory.getLog(logName);
334         out.println(MARKER
335             + "Log Class: <b>" + log.getClass().getName() +"</b><br />");
336         if (level != null) {
337           out.println(MARKER + "Submitted Level: <b>" + level + "</b><br />");
338         }
339 
340         if (log instanceof Log4JLogger) {
341           process(((Log4JLogger)log).getLogger(), level, out);
342         }
343         else if (log instanceof Jdk14Logger) {
344           process(((Jdk14Logger)log).getLogger(), level, out);
345         }
346         else {
347           out.println("Sorry, " + log.getClass() + " not supported.<br />");
348         }
349       }
350 
351       out.println(FORMS);
352       out.println(ServletUtil.HTML_TAIL);
353     }
354 
355     static final String FORMS = "\n<br /><hr /><h3>Get / Set</h3>"
356         + "\n<form>Log: <input type='text' size='50' name='log' /> "
357         + "<input type='submit' value='Get Log Level' />"
358         + "</form>"
359         + "\n<form>Log: <input type='text' size='50' name='log' /> "
360         + "Level: <input type='text' name='level' /> "
361         + "<input type='submit' value='Set Log Level' />"
362         + "</form>";
363 
364     private static void process(org.apache.log4j.Logger log, String level, PrintWriter out) {
365       if (level != null) {
366         if (!level.equals(org.apache.log4j.Level.toLevel(level).toString())) {
367           out.println(MARKER + "Bad level : <b>" + level + "</b><br />");
368         } else {
369           log.setLevel(org.apache.log4j.Level.toLevel(level));
370           out.println(MARKER + "Setting Level to " + level + " ...<br />");
371         }
372       }
373       out.println(MARKER
374           + "Effective level: <b>" + log.getEffectiveLevel() + "</b><br />");
375     }
376 
377     private static void process(java.util.logging.Logger log, String level,
378         PrintWriter out) {
379       if (level != null) {
380         log.setLevel(java.util.logging.Level.parse(level));
381         out.println(MARKER + "Setting Level to " + level + " ...<br />");
382       }
383 
384       java.util.logging.Level lev;
385       while ((lev = log.getLevel()) == null) {
386         log = log.getParent();
387       }
388       out.println(MARKER + "Effective level: <b>" + lev + "</b><br />");
389     }
390   }
391 }