1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 
28 package java.util.logging;
29 
30 import java.io.*;
31 import java.net.*;
32 import libcore.net.NetworkSecurityPolicy;
33 
34 /**
35  * Simple network logging <tt>Handler</tt>.
36  * <p>
37  * <tt>LogRecords</tt> are published to a network stream connection.  By default
38  * the <tt>XMLFormatter</tt> class is used for formatting.
39  * <p>
40  * <b>Configuration:</b>
41  * By default each <tt>SocketHandler</tt> is initialized using the following
42  * <tt>LogManager</tt> configuration properties where <tt>&lt;handler-name&gt;</tt>
43  * refers to the fully-qualified class name of the handler.
44  * If properties are not defined
45  * (or have invalid values) then the specified default values are used.
46  * <ul>
47  * <li>   &lt;handler-name&gt;.level
48  *        specifies the default level for the <tt>Handler</tt>
49  *        (defaults to <tt>Level.ALL</tt>). </li>
50  * <li>   &lt;handler-name&gt;.filter
51  *        specifies the name of a <tt>Filter</tt> class to use
52  *        (defaults to no <tt>Filter</tt>). </li>
53  * <li>   &lt;handler-name&gt;.formatter
54  *        specifies the name of a <tt>Formatter</tt> class to use
55  *        (defaults to <tt>java.util.logging.XMLFormatter</tt>). </li>
56  * <li>   &lt;handler-name&gt;.encoding
57  *        the name of the character set encoding to use (defaults to
58  *        the default platform encoding). </li>
59  * <li>   &lt;handler-name&gt;.host
60  *        specifies the target host name to connect to (no default). </li>
61  * <li>   &lt;handler-name&gt;.port
62  *        specifies the target TCP port to use (no default). </li>
63  * </ul>
64  * <p>
65  * For example, the properties for {@code SocketHandler} would be:
66  * <ul>
67  * <li>   java.util.logging.SocketHandler.level=INFO </li>
68  * <li>   java.util.logging.SocketHandler.formatter=java.util.logging.SimpleFormatter </li>
69  * </ul>
70  * <p>
71  * For a custom handler, e.g. com.foo.MyHandler, the properties would be:
72  * <ul>
73  * <li>   com.foo.MyHandler.level=INFO </li>
74  * <li>   com.foo.MyHandler.formatter=java.util.logging.SimpleFormatter </li>
75  * </ul>
76  * <p>
77  * The output IO stream is buffered, but is flushed after each
78  * <tt>LogRecord</tt> is written.
79  *
80  * @since 1.4
81  */
82 
83 public class SocketHandler extends StreamHandler {
84     private Socket sock;
85     private String host;
86     private int port;
87 
88     // Private method to configure a SocketHandler from LogManager
89     // properties and/or default values as specified in the class
90     // javadoc.
configure()91     private void configure() {
92         LogManager manager = LogManager.getLogManager();
93         String cname = getClass().getName();
94 
95         setLevel(manager.getLevelProperty(cname +".level", Level.ALL));
96         setFilter(manager.getFilterProperty(cname +".filter", null));
97         setFormatter(manager.getFormatterProperty(cname +".formatter", new XMLFormatter()));
98         try {
99             setEncoding(manager.getStringProperty(cname +".encoding", null));
100         } catch (Exception ex) {
101             try {
102                 setEncoding(null);
103             } catch (Exception ex2) {
104                 // doing a setEncoding with null should always work.
105                 // assert false;
106             }
107         }
108         port = manager.getIntProperty(cname + ".port", 0);
109         host = manager.getStringProperty(cname + ".host", null);
110     }
111 
112 
113     /**
114      * Create a <tt>SocketHandler</tt>, using only <tt>LogManager</tt> properties
115      * (or their defaults).
116      * @throws IllegalArgumentException if the host or port are invalid or
117      *          are not specified as LogManager properties.
118      * @throws IOException if we are unable to connect to the target
119      *         host and port.
120      */
SocketHandler()121     public SocketHandler() throws IOException {
122         // We are going to use the logging defaults.
123         sealed = false;
124         configure();
125 
126         try {
127             connect();
128         } catch (IOException ix) {
129             System.err.println("SocketHandler: connect failed to " + host + ":" + port);
130             throw ix;
131         }
132         sealed = true;
133     }
134 
135     /**
136      * Construct a <tt>SocketHandler</tt> using a specified host and port.
137      *
138      * The <tt>SocketHandler</tt> is configured based on <tt>LogManager</tt>
139      * properties (or their default values) except that the given target host
140      * and port arguments are used. If the host argument is empty, but not
141      * null String then the localhost is used.
142      *
143      * @param host target host.
144      * @param port target port.
145      *
146      * @throws IllegalArgumentException if the host or port are invalid.
147      * @throws IOException if we are unable to connect to the target
148      *         host and port.
149      */
SocketHandler(String host, int port)150     public SocketHandler(String host, int port) throws IOException {
151         sealed = false;
152         configure();
153         sealed = true;
154         this.port = port;
155         this.host = host;
156         connect();
157     }
158 
connect()159     private void connect() throws IOException {
160         // Check the arguments are valid.
161         if (port == 0) {
162             throw new IllegalArgumentException("Bad port: " + port);
163         }
164         if (host == null) {
165             throw new IllegalArgumentException("Null host name: " + host);
166         }
167 
168         // Android-added: Enforce cleartext policy.
169         if (!NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()) {
170             throw new IOException("Cleartext traffic not permitted");
171         }
172 
173         // Try to open a new socket.
174         sock = new Socket(host, port);
175         OutputStream out = sock.getOutputStream();
176         BufferedOutputStream bout = new BufferedOutputStream(out);
177         setOutputStream(bout);
178     }
179 
180     /**
181      * Close this output stream.
182      *
183      * @exception  SecurityException  if a security manager exists and if
184      *             the caller does not have <tt>LoggingPermission("control")</tt>.
185      */
186     @Override
close()187     public synchronized void close() throws SecurityException {
188         super.close();
189         if (sock != null) {
190             try {
191                 sock.close();
192             } catch (IOException ix) {
193                 // drop through.
194             }
195         }
196         sock = null;
197     }
198 
199     /**
200      * Format and publish a <tt>LogRecord</tt>.
201      *
202      * @param  record  description of the log event. A null record is
203      *                 silently ignored and is not published
204      */
205     @Override
publish(LogRecord record)206     public synchronized void publish(LogRecord record) {
207         if (!isLoggable(record)) {
208             return;
209         }
210         super.publish(record);
211         flush();
212     }
213 }
214