001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.internal;
014
015import static java.util.logging.Level.*;
016import static org.apache.juneau.internal.StringUtils.*;
017
018import java.text.*;
019import java.util.*;
020import java.util.concurrent.*;
021import java.util.logging.*;
022
023import org.apache.juneau.json.*;
024import org.apache.juneau.serializer.*;
025
026/**
027 * Wraps and extends the {@link java.util.logging.Logger} class to provide some additional convenience methods.
028 */
029public class JuneauLogger extends java.util.logging.Logger {
030
031   private static final WriterSerializer serializer = JsonSerializer.create().ssq().build();
032
033   private static final ConcurrentHashMap<Class<?>,String> rbMap = new ConcurrentHashMap<>();
034
035   private final ResourceBundle rb;
036   private final java.util.logging.Logger innerLogger;
037
038   /**
039    * Get logger for specified class.
040    *
041    * @param forClass The class to create a logger for.
042    * @return A new <l>Logger</l>.
043    */
044   public static JuneauLogger getLogger(Class<?> forClass) {
045      return getLogger(forClass.getName());
046   }
047
048   /**
049    * Get logger for specified class.
050    *
051    * @param loggerName The logger name.
052    * @return A new <l>Logger</l>.
053    */
054   public static JuneauLogger getLogger(String loggerName) {
055      return new JuneauLogger(java.util.logging.Logger.getLogger(loggerName));
056   }
057
058   /**
059    * Get logger for specified class using the specified resource bundle name.
060    *
061    * @param forClass The class to create a logger for.
062    * @param resourceBundleName
063    *    The name of the resource bundle.
064    *    Can be any of the following formats:
065    *    <ol>
066    *       <li>An absolute path.  E.g. <js>"com/foo/nls/Messages"</js>.
067    *       <li>A path relative to the package of the class.  E.g. <js>"nls/Messages"</js>.
068    *    </ol>
069    *    Both <js>'.'</js> and <js>'/'</js> can be used as path delimiters.
070    * @return A new <l>Logger</l>.
071    */
072   public static JuneauLogger getLogger(Class<?> forClass, String resourceBundleName) {
073      return new JuneauLogger(java.util.logging.Logger.getLogger(forClass.getName(), resolveResourceBundleName(forClass, resourceBundleName)));
074   }
075
076   /**
077    * Get logger with specified name using the specified resource bundle name.
078    *
079    * @param name The name of the logger to use.
080    * @param resourceBundleName
081    *    The name of the resource bundle.
082    *    Can be any of the following formats:
083    *    <ol>
084    *       <li>An absolute path.  E.g. <js>"com/foo/nls/Messages"</js>.
085    *       <li>A path relative to the package of the class.  E.g. <js>"nls/Messages"</js>.
086    *    </ol>
087    *    Both <js>'.'</js> and <js>'/'</js> can be used as path delimiters.
088    * @return A new <l>Logger</l>.
089    */
090   public static synchronized JuneauLogger getLogger(String name, String resourceBundleName) {
091      return new JuneauLogger(java.util.logging.Logger.getLogger(name, resourceBundleName));
092   }
093
094   /**
095    * Constructor.
096    *
097    * @param innerLogger The wrapped logger.
098    */
099   protected JuneauLogger(java.util.logging.Logger innerLogger) {
100      super(innerLogger.getName(), innerLogger.getResourceBundleName());
101      this.innerLogger = innerLogger;
102      this.rb = getResourceBundle();
103   }
104
105   /**
106    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#SEVERE} level.
107    *
108    * @param msg The message to log.
109    * @param args Optional {@link MessageFormat}-style arguments.
110    */
111   public void severe(String msg, Object...args) {
112      if (isLoggable(SEVERE))
113         log(SEVERE, msg, args);
114   }
115
116   /**
117    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#WARNING} level.
118    *
119    * @param msg The message to log.
120    * @param args Optional {@link MessageFormat}-style arguments.
121    */
122   public void warning(String msg, Object...args) {
123      if (isLoggable(WARNING))
124         log(WARNING, msg, args);
125   }
126
127   /**
128    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#INFO} level.
129    *
130    * @param msg The message to log.
131    * @param args Optional {@link MessageFormat}-style arguments.
132    */
133   public void info(String msg, Object...args) {
134      if (isLoggable(INFO))
135         log(INFO, msg, args);
136   }
137
138   /**
139    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#CONFIG} level.
140    *
141    * @param msg The message to log.
142    * @param args Optional {@link MessageFormat}-style arguments.
143    */
144   public void config(String msg, Object...args) {
145      if (isLoggable(CONFIG))
146         log(CONFIG, msg, args);
147   }
148
149   /**
150    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#FINE} level.
151    *
152    * @param msg The message to log.
153    * @param args Optional {@link MessageFormat}-style arguments.
154    */
155   public void fine(String msg, Object...args) {
156      if (isLoggable(FINE))
157         log(FINE, msg, args);
158   }
159
160   /**
161    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#FINER} level.
162    *
163    * @param msg The message to log.
164    * @param args Optional {@link MessageFormat}-style arguments.
165    */
166   public void finer(String msg, Object...args) {
167      if (isLoggable(FINER))
168         log(FINER, msg, args);
169   }
170
171   /**
172    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#FINEST} level.
173    *
174    * @param msg The message to log.
175    * @param args Optional {@link MessageFormat}-style arguments.
176    */
177   public void finest(String msg, Object...args) {
178      if (isLoggable(FINEST))
179         log(FINEST, msg, args);
180   }
181
182   /**
183    * Logs an exception as {@link Level#SEVERE} level.
184    *
185    * @param t The Throwable object to log.
186    */
187   public void severe(Throwable t) {
188      if (isLoggable(SEVERE))
189         log(SEVERE, t.getLocalizedMessage(), t);
190   }
191
192   /**
193    * Logs an exception as {@link Level#WARNING} level.
194    *
195    * @param t The Throwable object to log.
196    */
197   public void warning(Throwable t) {
198      if (isLoggable(WARNING))
199         log(WARNING, t.getLocalizedMessage(), t);
200   }
201
202   /**
203    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#SEVERE} level.
204    *
205    * @param t The Throwable object associated with the event that needs to be logged.
206    * @param msg The message to log.
207    * @param args Optional {@link MessageFormat}-style arguments.
208    */
209   public void severe(Throwable t, String msg, Object...args) {
210      if (isLoggable(SEVERE))
211         log(SEVERE, getMessage(msg, args), t);
212   }
213
214   /**
215    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#WARNING} level.
216    *
217    * @param t The Throwable object associated with the event that needs to be logged.
218    * @param msg The message to log.
219    * @param args Optional {@link MessageFormat}-style arguments.
220    */
221   public void warning(Throwable t, String msg, Object...args) {
222      if (isLoggable(WARNING))
223         log(WARNING, getMessage(msg, args), t);
224   }
225
226   /**
227    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#INFO} level.
228    *
229    * @param t The Throwable object associated with the event that needs to be logged.
230    * @param msg The message to log.
231    * @param args Optional {@link MessageFormat}-style arguments.
232    */
233   public void info(Throwable t, String msg, Object...args) {
234      if (isLoggable(INFO))
235         log(INFO, getMessage(msg, args), t);
236   }
237
238   @Override /* Logger */
239   public void log(LogRecord record) {
240      innerLogger.log(record);
241   }
242
243   /**
244    * Logs a message with the specified {@link MessageFormat}-style arguments at the specified level.
245    *
246    * @param level The log level.
247    * @param cause The Throwable object associated with the event that needs to be logged.
248    * @param msg The message to log.
249    * @param args Optional {@link MessageFormat}-style arguments.
250    */
251   public void log(Level level, Throwable cause, String msg, Object...args) {
252      if (isLoggable(level))
253         log(level, getMessage(msg, args), cause);
254   }
255
256   @Override /* Logger */
257   public boolean isLoggable(Level level) {
258      return innerLogger.isLoggable(level);
259   }
260
261   /**
262    * Similar to {@link #log(Level, String, Object[])}, except arguments are converted to objects
263    * that are serialized using the {@link JsonSerializer#toStringObject(Object)} method.
264    *
265    * <p>
266    * This allows arbitrary POJOs to be serialized as message parameters.
267    *
268    * @param level The level of the given message.
269    * @param msg The message to log.
270    * @param args The POJO arguments.
271    */
272   public void logObjects(Level level, String msg, Object...args) {
273      if (isLoggable(level)) {
274         for (int i = 0; i < args.length; i++)
275            args[i] = serializer.toStringObject(args[i]);
276         log(level, msg, args);
277      }
278   }
279
280   private String getMessage(String msg, Object...args) {
281      if (args.length == 0)
282         return msg;
283      if (rb != null && rb.containsKey(msg))
284         msg = rb.getString(msg);
285      return format(msg, args);
286   }
287
288   private static String resolveResourceBundleName(Class<?> forClass, String path) {
289      if (isEmpty(path))
290         return null;
291      String rb = rbMap.get(forClass);
292      if (rb == null) {
293         path = path.replace('/', '.');
294         if (path.startsWith("."))
295            path = path.substring(1);
296         ClassLoader cl = forClass.getClassLoader();
297         try {
298            ResourceBundle.getBundle(path, Locale.getDefault(), cl);
299            rbMap.putIfAbsent(forClass, path);
300         } catch (MissingResourceException e) {
301            try {
302               path = forClass.getPackage().getName() + '.' + path;
303               ResourceBundle.getBundle(path, Locale.getDefault(), cl);
304               rbMap.putIfAbsent(forClass, path);
305            } catch (MissingResourceException e2) {
306               rbMap.putIfAbsent(forClass, "");
307            }
308         }
309         rb = rbMap.get(forClass);
310      }
311      return ("".equals(rb) ? null : rb);
312   }
313}