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    }