001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.felix.framework;
020
021 import java.lang.reflect.InvocationTargetException;
022 import java.lang.reflect.Method;
023 import org.osgi.framework.*;
024
025 /**
026 * <p>
027 * This class mimics the standard OSGi <tt>LogService</tt> interface. An
028 * instance of this class is used by the framework for all logging. By default
029 * this class logs messages to standard out. The log level can be set to
030 * control the amount of logging performed, where a higher number results in
031 * more logging. A log level of zero turns off logging completely.
032 * </p>
033 * <p>
034 * The log levels match those specified in the OSGi Log Service (i.e., 1 = error,
035 * 2 = warning, 3 = information, and 4 = debug). The default value is 1.
036 * </p>
037 * <p>
038 * This class also uses the System Bundle's context to track log services
039 * and will use the highest ranking log service, if present, as a back end
040 * instead of printing to standard out. The class uses reflection to invoking
041 * the log service's method to avoid a dependency on the log interface.
042 * </p>
043 **/
044 public class Logger implements ServiceListener
045 {
046 public static final int LOG_ERROR = 1;
047 public static final int LOG_WARNING = 2;
048 public static final int LOG_INFO = 3;
049 public static final int LOG_DEBUG = 4;
050
051 private int m_logLevel = 1;
052 private BundleContext m_context = null;
053
054 private final static int LOGGER_OBJECT_IDX = 0;
055 private final static int LOGGER_METHOD_IDX = 1;
056 private ServiceReference m_logRef = null;
057 private Object[] m_logger = null;
058
059 public Logger()
060 {
061 }
062
063 public final synchronized void setLogLevel(int i)
064 {
065 m_logLevel = i;
066 }
067
068 public final synchronized int getLogLevel()
069 {
070 return m_logLevel;
071 }
072
073 protected void setSystemBundleContext(BundleContext context)
074 {
075 // TODO: Find a way to log to a log service inside the framework.
076 // The issue is that we log messages while holding framework
077 // internal locks -- hence, when a log service calls back into
078 // the framework (e.g., by loading a class) we might deadlock.
079 // One instance of this problem is tracked in FELIX-536.
080 // For now we just disable logging to log services inside the
081 // framework.
082
083 // m_context = context;
084 // startListeningForLogService();
085 }
086
087 public final void log(int level, String msg)
088 {
089 _log(null, level, msg, null);
090 }
091
092 public final void log(int level, String msg, Throwable throwable)
093 {
094 _log(null, level, msg, throwable);
095 }
096
097 public final void log(ServiceReference sr, int level, String msg)
098 {
099 _log(sr, level, msg, null);
100 }
101
102 public final void log(ServiceReference sr, int level, String msg, Throwable throwable)
103 {
104 _log(sr, level, msg, throwable);
105 }
106
107 protected void doLog(ServiceReference sr, int level, String msg, Throwable throwable)
108 {
109 String s = (sr == null) ? null : "SvcRef " + sr;
110 s = (s == null) ? msg : s + " " + msg;
111 s = (throwable == null) ? s : s + " (" + throwable + ")";
112 switch (level)
113 {
114 case LOG_DEBUG:
115 System.out.println("DEBUG: " + s);
116 break;
117 case LOG_ERROR:
118 System.out.println("ERROR: " + s);
119 if (throwable != null)
120 {
121 if ((throwable instanceof BundleException) &&
122 (((BundleException) throwable).getNestedException() != null))
123 {
124 throwable = ((BundleException) throwable).getNestedException();
125 }
126 throwable.printStackTrace();
127 }
128 break;
129 case LOG_INFO:
130 System.out.println("INFO: " + s);
131 break;
132 case LOG_WARNING:
133 System.out.println("WARNING: " + s);
134 break;
135 default:
136 System.out.println("UNKNOWN[" + level + "]: " + s);
137 }
138 }
139
140 private void _log(ServiceReference sr, int level, String msg, Throwable throwable)
141 {
142 // Save our own copy just in case it changes. We could try to do
143 // more conservative locking here, but let's be optimistic.
144 Object[] logger = m_logger;
145
146 if (m_logLevel >= level)
147 {
148 // Use the log service if available.
149 if (logger != null)
150 {
151 _logReflectively(logger, sr, level, msg, throwable);
152 }
153 // Otherwise, default logging action.
154 else
155 {
156 doLog(sr, level, msg, throwable);
157 }
158 }
159 }
160
161 private void _logReflectively(
162 Object[] logger, ServiceReference sr, int level, String msg, Throwable throwable)
163 {
164 if (logger != null)
165 {
166 Object[] params = {
167 sr, new Integer(level), msg, throwable
168 };
169 try
170 {
171 ((Method) logger[LOGGER_METHOD_IDX]).invoke(logger[LOGGER_OBJECT_IDX], params);
172 }
173 catch (InvocationTargetException ex)
174 {
175 System.err.println("Logger: " + ex);
176 }
177 catch (IllegalAccessException ex)
178 {
179 System.err.println("Logger: " + ex);
180 }
181 }
182 }
183
184 /**
185 * This method is called when the system bundle context is set;
186 * it simply adds a service listener so that the system bundle can track
187 * log services to be used as the back end of the logging mechanism. It also
188 * attempts to get an existing log service, if present, but in general
189 * there will never be a log service present since the system bundle is
190 * started before every other bundle.
191 **/
192 private synchronized void startListeningForLogService()
193 {
194 // Add a service listener for log services.
195 try
196 {
197 m_context.addServiceListener(
198 this, "(objectClass=org.osgi.service.log.LogService)");
199 }
200 catch (InvalidSyntaxException ex) {
201 // This will never happen since the filter is hard coded.
202 }
203 // Try to get an existing log service.
204 m_logRef = m_context.getServiceReference("org.osgi.service.log.LogService");
205 // Get the service object if available and set it in the logger.
206 if (m_logRef != null)
207 {
208 setLogger(m_context.getService(m_logRef));
209 }
210 }
211
212 /**
213 * This method implements the callback for the ServiceListener interface.
214 * It is public as a byproduct of implementing the interface and should
215 * not be called directly. This method tracks run-time changes to log
216 * service availability. If the log service being used by the framework's
217 * logging mechanism goes away, then this will try to find an alternative.
218 * If a higher ranking log service is registered, then this will switch
219 * to the higher ranking log service.
220 **/
221 public final synchronized void serviceChanged(ServiceEvent event)
222 {
223 // If no logger is in use, then grab this one.
224 if ((event.getType() == ServiceEvent.REGISTERED) && (m_logRef == null))
225 {
226 m_logRef = event.getServiceReference();
227 // Get the service object and set it in the logger.
228 setLogger(m_context.getService(m_logRef));
229 }
230 // If a logger is in use, but this one has a higher ranking, then swap
231 // it for the existing logger.
232 else if ((event.getType() == ServiceEvent.REGISTERED) && (m_logRef != null))
233 {
234 ServiceReference ref =
235 m_context.getServiceReference("org.osgi.service.log.LogService");
236 if (!ref.equals(m_logRef))
237 {
238 m_context.ungetService(m_logRef);
239 m_logRef = ref;
240 setLogger(m_context.getService(m_logRef));
241 }
242
243 }
244 // If the current logger is going away, release it and try to
245 // find another one.
246 else if ((event.getType() == ServiceEvent.UNREGISTERING) &&
247 m_logRef.equals(event.getServiceReference()))
248 {
249 // Unget the service object.
250 m_context.ungetService(m_logRef);
251 // Try to get an existing log service.
252 m_logRef = m_context.getServiceReference(
253 "org.osgi.service.log.LogService");
254 // Get the service object if available and set it in the logger.
255 if (m_logRef != null)
256 {
257 setLogger(m_context.getService(m_logRef));
258 }
259 else
260 {
261 setLogger(null);
262 }
263 }
264 }
265
266 /**
267 * This method sets the new log service object. It also caches the method to
268 * invoke. The service object and method are stored in array to optimistically
269 * eliminate the need to locking when logging.
270 **/
271 private void setLogger(Object logObj)
272 {
273 if (logObj == null)
274 {
275 m_logger = null;
276 }
277 else
278 {
279 Class[] formalParams = {
280 ServiceReference.class,
281 Integer.TYPE,
282 String.class,
283 Throwable.class
284 };
285
286 try
287 {
288 Method logMethod = logObj.getClass().getMethod("log", formalParams);
289 logMethod.setAccessible(true);
290 m_logger = new Object[] { logObj, logMethod };
291 }
292 catch (NoSuchMethodException ex)
293 {
294 System.err.println("Logger: " + ex);
295 m_logger = null;
296 }
297 }
298 }
299 }