1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.util.logging;
19
20import java.io.BufferedOutputStream;
21import java.io.IOException;
22import java.net.Socket;
23
24/**
25 * A handler that writes log messages to a socket connection.
26 * <p>
27 * This handler reads the following properties from the log manager to
28 * initialize itself:
29 * <ul>
30 * <li>java.util.logging.ConsoleHandler.level specifies the logging level,
31 * defaults to {@code Level.ALL} if this property is not found or has an invalid
32 * value.
33 * <li>java.util.logging.SocketHandler.filter specifies the name of the filter
34 * class to be associated with this handler, defaults to {@code null} if this
35 * property is not found or has an invalid value.
36 * <li>java.util.logging.SocketHandler.formatter specifies the name of the
37 * formatter class to be associated with this handler, defaults to
38 * {@code java.util.logging.XMLFormatter} if this property is not found or has
39 * an invalid value.
40 * <li>java.util.logging.SocketHandler.encoding specifies the encoding this
41 * handler will use to encode log messages, defaults to {@code null} if this
42 * property is not found or has an invalid value.
43 * <li>java.util.logging.SocketHandler.host specifies the name of the host that
44 * this handler should connect to. There's no default value for this property.
45 * <li>java.util.logging.SocketHandler.encoding specifies the port number that
46 * this handler should connect to. There's no default value for this property.
47 * </ul>
48 * <p>
49 * This handler buffers the outgoing messages, but flushes each time a log
50 * record has been published.
51 * <p>
52 * This class is not thread-safe.
53 */
54public class SocketHandler extends StreamHandler {
55
56    // default level
57    private static final String DEFAULT_LEVEL = "ALL";
58
59    // default formatter
60    private static final String DEFAULT_FORMATTER = "java.util.logging.XMLFormatter";
61
62    // the socket connection
63    private Socket socket;
64
65    /**
66     * Constructs a {@code SocketHandler} object using the properties read by
67     * the log manager, including the host name and port number. Default
68     * formatting uses the XMLFormatter class and level is set to ALL.
69     *
70     * @throws IOException
71     *             if failed to connect to the specified host and port.
72     * @throws IllegalArgumentException
73     *             if the host name or port number is illegal.
74     */
75    public SocketHandler() throws IOException {
76        super(DEFAULT_LEVEL, null, DEFAULT_FORMATTER, null);
77        initSocket(LogManager.getLogManager().getProperty(
78                "java.util.logging.SocketHandler.host"), LogManager
79                .getLogManager().getProperty(
80                        "java.util.logging.SocketHandler.port"));
81    }
82
83    /**
84     * Constructs a {@code SocketHandler} object using the specified host name
85     * and port number together with other properties read by the log manager.
86     * Default formatting uses the XMLFormatter class and level is set to ALL.
87     *
88     * @param host
89     *            the host name
90     * @param port
91     *            the port number
92     * @throws IOException
93     *             if failed to connect to the specified host and port.
94     * @throws IllegalArgumentException
95     *             if the host name or port number is illegal.
96     */
97    public SocketHandler(String host, int port) throws IOException {
98        super(DEFAULT_LEVEL, null, DEFAULT_FORMATTER, null);
99        initSocket(host, String.valueOf(port));
100    }
101
102    // Initialize the socket connection and prepare the output stream
103    private void initSocket(String host, String port) throws IOException {
104        // check the validity of the host name
105        if (host == null || host.isEmpty()) {
106            throw new IllegalArgumentException("host == null || host.isEmpty()");
107        }
108        // check the validity of the port number
109        int p = 0;
110        try {
111            p = Integer.parseInt(port);
112        } catch (NumberFormatException e) {
113            throw new IllegalArgumentException("Illegal port argument");
114        }
115        if (p <= 0) {
116            throw new IllegalArgumentException("Illegal port argument");
117        }
118        // establish the network connection
119        try {
120            this.socket = new Socket(host, p);
121        } catch (IOException e) {
122            getErrorManager().error("Failed to establish the network connection", e,
123                    ErrorManager.OPEN_FAILURE);
124            throw e;
125        }
126        super.internalSetOutputStream(new BufferedOutputStream(this.socket.getOutputStream()));
127    }
128
129    /**
130     * Closes this handler. The network connection to the host is also closed.
131     */
132    @Override
133    public void close() {
134        try {
135            super.close();
136            if (this.socket != null) {
137                this.socket.close();
138                this.socket = null;
139            }
140        } catch (Exception e) {
141            getErrorManager().error("Exception occurred when closing the socket handler", e,
142                    ErrorManager.CLOSE_FAILURE);
143        }
144    }
145
146    /**
147     * Logs a record if necessary. A flush operation will be done afterwards.
148     *
149     * @param record
150     *            the log record to be logged.
151     */
152    @Override
153    public void publish(LogRecord record) {
154        super.publish(record);
155        super.flush();
156    }
157}
158