001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    
018    package org.apache.geronimo.system.logging.log4j;
019    
020    import java.io.File;
021    import java.io.FileInputStream;
022    import java.io.FileOutputStream;
023    import java.io.IOException;
024    import java.io.InputStreamReader;
025    import java.io.OutputStream;
026    import java.io.RandomAccessFile;
027    import java.io.Reader;
028    import java.net.MalformedURLException;
029    import java.nio.CharBuffer;
030    import java.nio.MappedByteBuffer;
031    import java.nio.channels.FileChannel;
032    import java.nio.charset.Charset;
033    import java.util.ArrayList;
034    import java.util.Enumeration;
035    import java.util.Iterator;
036    import java.util.LinkedList;
037    import java.util.List;
038    import java.util.Set;
039    import java.util.Timer;
040    import java.util.TimerTask;
041    import java.util.regex.Matcher;
042    import java.util.regex.Pattern;
043    import java.util.regex.PatternSyntaxException;
044    
045    import org.apache.commons.logging.Log;
046    import org.apache.commons.logging.LogConfigurationException;
047    import org.apache.commons.logging.LogFactory;
048    import org.apache.geronimo.gbean.GBeanInfo;
049    import org.apache.geronimo.gbean.GBeanInfoBuilder;
050    import org.apache.geronimo.gbean.GBeanLifecycle;
051    import org.apache.geronimo.kernel.log.GeronimoLogFactory;
052    import org.apache.geronimo.kernel.log.GeronimoLogging;
053    import org.apache.geronimo.system.logging.SystemLog;
054    import org.apache.geronimo.system.serverinfo.DirectoryUtils;
055    import org.apache.geronimo.system.serverinfo.ServerConstants;
056    import org.apache.geronimo.system.serverinfo.ServerInfo;
057    import org.apache.log4j.FileAppender;
058    import org.apache.log4j.Level;
059    import org.apache.log4j.LogManager;
060    import org.apache.log4j.Logger;
061    
062    /**
063     * A Log4j logging service.
064     *
065     * @version $Rev: 550523 $ $Date: 2007-06-25 11:02:09 -0400 (Mon, 25 Jun 2007) $
066     */
067    public class Log4jService implements GBeanLifecycle, SystemLog {
068        // A substitution variable in the file path in the config file
069        private final static Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{.*?\\}");
070        // Next 6 are patterns that identify log messages in our default format
071        private final static Pattern DEFAULT_ANY_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d (TRACE|DEBUG|INFO|WARN|ERROR|FATAL) .*");
072        private final static Pattern DEFAULT_FATAL_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d FATAL .*");
073        private final static Pattern DEFAULT_ERROR_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d (ERROR|FATAL) .*");
074        private final static Pattern DEFAULT_WARN_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d (WARN|ERROR|FATAL) .*");
075        private final static Pattern DEFAULT_INFO_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d (INFO|WARN|ERROR|FATAL) .*");
076        private final static Pattern DEFAULT_DEBUG_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d (DEBUG|INFO|WARN|ERROR|FATAL) .*");
077        // Next 6 are patterns that identify log messages if the user changed the format -- but we assume the log level is in there somewhere
078        private final static Pattern UNKNOWN_ANY_START = Pattern.compile("(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)");
079        private final static Pattern UNKNOWN_FATAL_START = Pattern.compile("FATAL");
080        private final static Pattern UNKNOWN_ERROR_START = Pattern.compile("(ERROR|FATAL)");
081        private final static Pattern UNKNOWN_WARN_START = Pattern.compile("(WARN|ERROR|FATAL)");
082        private final static Pattern UNKNOWN_INFO_START = Pattern.compile("(INFO|WARN|ERROR|FATAL)");
083        private final static Pattern UNKNOWN_DEBUG_START = Pattern.compile("(DEBUG|INFO|WARN|ERROR|FATAL)");
084        // Pattern that matches a single line  (used to calculate line numbers and check for follow-on stack traces)
085        private final static Pattern FULL_LINE_PATTERN = Pattern.compile("^.*", Pattern.MULTILINE);
086    
087        private final static Log log = LogFactory.getLog(Log4jService.class);
088    
089        /**
090         * The URL to the configuration file.
091         */
092        private String configurationFile;
093    
094        /**
095         * The time (in seconds) between checking for new config.
096         */
097        private int refreshPeriod;
098    
099        /**
100         * The properties service
101         */
102        private final ServerInfo serverInfo;
103    
104        /**
105         * The URL watch timer (in daemon mode).
106         */
107        private Timer timer = new Timer(true);
108    
109        /**
110         * A monitor to check when the config URL changes.
111         */
112        private TimerTask monitor;
113    
114        /**
115         * Last time the file was changed.
116         */
117        private long lastChanged = -1;
118    
119        /**
120         * Is this service running?
121         */
122        private boolean running = false;
123    
124        /**
125         * Construct a <code>Log4jService</code>.
126         *
127         * @param configurationFile The log4j configuration file.
128         * @param refreshPeriod The refresh refreshPeriod (in seconds).
129         */
130        public Log4jService(final String configurationFile, final int refreshPeriod, ServerInfo serverInfo) {
131            this.refreshPeriod = refreshPeriod;
132            this.configurationFile = configurationFile;
133            this.serverInfo = serverInfo;
134        }
135    
136        /**
137         * Gets the level of the root logger.
138         */
139        public synchronized String getRootLoggerLevel() {
140            Level level = LogManager.getRootLogger().getLevel();
141    
142            if (level != null) {
143                return level.toString();
144            }
145    
146            return null;
147        }
148    
149        /**
150         * Sets the level of the root logger.
151         *
152         * @param level The level to change the logger to.
153         */
154        public synchronized void setRootLoggerLevel(final String level) {
155    
156            String currentLevel = this.getRootLoggerLevel();
157    
158            // ensure that the level has really been changed
159            if (!currentLevel.equals(level)) {
160                LogManager.getRootLogger().setLevel(XLevel.toLevel(level));
161            }
162        }
163    
164        /**
165         * Gets the level of the logger of the give name.
166         *
167         * @param logger The logger to inspect.
168         */
169        public String getLoggerEffectiveLevel(final String logger) {
170            if (logger == null) {
171                throw new IllegalArgumentException("logger is null");
172            }
173    
174            Level level = LogManager.getLogger(logger).getEffectiveLevel();
175    
176            if (level != null) {
177                return level.toString();
178            }
179    
180            return null;
181        }
182    
183        /**
184         * Gets the level of the logger of the give name.
185         *
186         * @param logger The logger to inspect.
187         */
188        public String getLoggerLevel(final String logger) {
189            if (logger == null) {
190                throw new IllegalArgumentException("logger is null");
191            }
192    
193            Level level = LogManager.getLogger(logger).getLevel();
194    
195            if (level != null) {
196                return level.toString();
197            }
198    
199            return null;
200        }
201    
202        /**
203         * Sets the level for a logger of the give name.
204         *
205         * @param logger The logger to change level
206         * @param level The level to change the logger to.
207         */
208        public void setLoggerLevel(final String logger, final String level) {
209            if (logger == null) {
210                throw new IllegalArgumentException("logger is null");
211            }
212            if (level == null) {
213                throw new IllegalArgumentException("level is null");
214            }
215    
216            log.info("Setting logger level: logger=" + logger + ", level=" + level);
217            Logger.getLogger(logger).setLevel(XLevel.toLevel(level));
218        }
219    
220        /**
221         * Get the refresh period.
222         *
223         * @return the refresh period (in seconds)
224         */
225        public synchronized int getRefreshPeriodSeconds() {
226            return refreshPeriod;
227        }
228    
229        /**
230         * Set the refresh period.
231         *
232         * @param period the refresh period (in seconds)
233         * @throws IllegalArgumentException if refresh period is < 5
234         */
235        public synchronized void setRefreshPeriodSeconds(final int period) {
236            if (period < 5) {
237                throw new IllegalArgumentException("Refresh period must be at least 5 seconds");
238            }
239    
240            if (this.refreshPeriod != period) {
241                this.refreshPeriod = period;
242                schedule();
243            }
244        }
245    
246        /**
247         * Get the logging configuration URL.
248         *
249         * @return the logging configuration URL
250         */
251        public synchronized String getConfigFileName() {
252            return configurationFile;
253        }
254    
255        /**
256         * Set the logging configuration URL.
257         *
258         * @param configurationFile the logging configuration file
259         */
260        public synchronized void setConfigFileName(final String configurationFile) {
261            log.debug("setConfigFileName() called with configurationFile=" + configurationFile);
262            if (configurationFile == null) {
263                throw new IllegalArgumentException("configurationFile is null");
264            }
265    
266            // ensure that the file name has really been updated
267            if (!this.configurationFile.equals(configurationFile)) {
268                this.configurationFile = configurationFile;
269                lastChanged = -1;
270                reconfigure();
271            }
272        }
273    
274        /**
275         * Get the content of logging configuration file.
276         *
277         * @return the content of logging configuration file
278         */
279        public synchronized String getConfiguration() {
280            File file = resolveConfigurationFile();
281            if (file == null || !file.canRead()) {
282                return null;
283            }
284            Reader in = null;
285            try {
286                StringBuffer configuration = new StringBuffer();
287                in = new InputStreamReader(new FileInputStream(file));
288                char[] buffer = new char[4096];
289                for (int size = in.read(buffer); size >= 0; size = in.read(buffer)) {
290                    configuration.append(buffer, 0, size);
291                }
292                return configuration.toString();
293            } catch (IOException e) {
294                e.printStackTrace();
295            } finally {
296                if (in != null) {
297                    try {
298                        in.close();
299                    } catch (IOException e1) {
300                        e1.printStackTrace();
301                    }
302                }
303            }
304            return null;
305        }
306    
307        /**
308         * Overwrites the content of logging configuration file.
309         *
310         * @param configuration the new content of logging configuration file
311         */
312        public synchronized void setConfiguration(final String configuration) throws IOException {
313            if (configuration == null || configuration.length() == 0) {
314                throw new IllegalArgumentException("configuration is null or an empty string");
315            }
316    
317            File file = resolveConfigurationFile();
318            if (file == null) {
319                throw new IllegalStateException("Configuration file is null");
320            }
321    
322            // make parent directory if necessary
323            if (!file.getParentFile().exists()) {
324                if (!file.getParentFile().mkdirs()) {
325                    throw new IllegalStateException("Could not create parent directory of log configuration file: " + file.getParent());
326                }
327            }
328    
329            // verify that the file is writable or does not exist
330            if (file.exists() && !file.canWrite()) {
331                throw new IllegalStateException("Configuration file is not writable: " + file.getAbsolutePath());
332            }
333    
334            OutputStream out = null;
335            try {
336                out = new FileOutputStream(file);
337                out.write(configuration.getBytes());
338                log.info("Updated configuration file=" + file.toString());
339            } finally {
340                if (out != null) {
341                    try {
342                        out.close();
343                    } catch (IOException e) {
344                        e.printStackTrace();
345                    }
346                }
347            }
348        }
349    
350        public synchronized String[] getLogFileNames() {
351            List list = new ArrayList();
352            for (Enumeration e = Logger.getRootLogger().getAllAppenders(); e.hasMoreElements();) {
353                Object appender = e.nextElement();
354                if (appender instanceof FileAppender) {
355                    list.add(((FileAppender) appender).getFile());
356                }
357            }
358            return (String[]) list.toArray(new String[list.size()]);
359        }
360    
361        private static SearchResults searchFile(File file, String targetLevel, Pattern textSearch, Integer start, Integer stop, int max, boolean stacks) {
362            List list = new LinkedList();
363            boolean capped = false;
364            int lineCount = 0;
365            try {
366                RandomAccessFile raf = new RandomAccessFile(file, "r");
367                FileChannel fc = raf.getChannel();
368                MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
369                CharBuffer cb = Charset.forName(System.getProperty("file.encoding")).decode(bb);
370                Matcher target = null;
371                Matcher any = null;
372                Matcher lines = FULL_LINE_PATTERN.matcher(cb);
373                Matcher text = textSearch == null ? null : textSearch.matcher("");
374                boolean hit = false;
375                max = Math.min(max, MAX_SEARCH_RESULTS);
376                while(lines.find()) {
377                    ++lineCount;
378                    if(target == null) {
379                        if(DEFAULT_ANY_START.matcher(cb.subSequence(lines.start(), lines.end())).find()) {
380                            target = getDefaultPatternForLevel(targetLevel).matcher("");
381                            any = DEFAULT_ANY_START.matcher("");
382                        } else {
383                            target = getUnknownPatternForLevel(targetLevel).matcher("");
384                            any = UNKNOWN_ANY_START.matcher("");
385                        }
386                    }
387                    if(start != null && start.intValue() > lineCount) {
388                        continue;
389                    }
390                    if(stop != null && stop.intValue() < lineCount) {
391                        continue;
392                    }
393                    CharSequence line = cb.subSequence(lines.start(), lines.end());
394                    target.reset(line);
395                    if(target.find()) {
396                        if(text != null) {
397                            text.reset(line);
398                            if(!text.find()) {
399                                hit = false;
400                                continue;
401                            }
402                        }
403                        list.add(new LogMessage(lineCount,line.toString()));
404                        if(list.size() > max) {
405                            list.remove(0);
406                            capped = true;
407                        }
408                        hit = true;
409                    } else if(stacks && hit) {
410                        any.reset(line);
411                        if(!any.find()) {
412                            list.add(new LogMessage(lineCount,line.toString()));
413                            if(list.size() > max) {
414                                list.remove(0);
415                                capped = true;
416                            }
417                        } else {
418                            hit = false;
419                        }
420                    }
421                }
422                fc.close();
423                raf.close();
424            } catch (Exception e) {} // TODO: improve exception handling
425            return new SearchResults(lineCount, (LogMessage[]) list.toArray(new LogMessage[list.size()]), capped);
426        }
427    
428        private static String substituteSystemProps(String source) {
429            StringBuffer buf = new StringBuffer();
430            int last = 0;
431            Matcher m = VARIABLE_PATTERN.matcher(source);
432            while(m.find()) {
433                buf.append(source.substring(last, m.start()));
434                String prop = source.substring(m.start()+2, m.end()-1);
435                buf.append(System.getProperty(prop));
436                last = m.end();
437            }
438            buf.append(source.substring(last));
439            return buf.toString();
440        }
441    
442        private static Pattern getDefaultPatternForLevel(String targetLevel) {
443            if(targetLevel.equals("FATAL")) {
444                return DEFAULT_FATAL_START;
445            } else if(targetLevel.equals("ERROR")) {
446                return DEFAULT_ERROR_START;
447            } else if(targetLevel.equals("WARN")) {
448                return DEFAULT_WARN_START;
449            } else if(targetLevel.equals("INFO")) {
450                return DEFAULT_INFO_START;
451            } else if(targetLevel.equals("DEBUG")) {
452                return DEFAULT_DEBUG_START;
453            } else {
454                return DEFAULT_ANY_START;
455            }
456        }
457    
458        private static Pattern getUnknownPatternForLevel(String targetLevel) {
459            if(targetLevel.equals("FATAL")) {
460                return UNKNOWN_FATAL_START;
461            } else if(targetLevel.equals("ERROR")) {
462                return UNKNOWN_ERROR_START;
463            } else if(targetLevel.equals("WARN")) {
464                return UNKNOWN_WARN_START;
465            } else if(targetLevel.equals("INFO")) {
466                return UNKNOWN_INFO_START;
467            } else if(targetLevel.equals("DEBUG")) {
468                return UNKNOWN_DEBUG_START;
469            } else {
470                return UNKNOWN_ANY_START;
471            }
472        }
473    
474        public SearchResults getMatchingItems(String logFile, Integer firstLine, Integer lastLine, String minLevel, String text, int maxResults, boolean includeStackTraces) {
475            // Ensure the file argument is really a log file!
476            if(logFile == null) {
477                throw new IllegalArgumentException("Must specify a log file");
478            }
479            String[] files = getLogFileNames();
480            boolean found = false;
481            for (int i = 0; i < files.length; i++) {
482                if(files[i].equals(logFile)) {
483                    found = true;
484                    break;
485                }
486            }
487            if(!found) {
488                throw new IllegalArgumentException("Not a log file!");
489            }
490            // Check for valid log level
491            if(minLevel == null) {
492                minLevel = "TRACE";
493            } else if(!minLevel.equals("FATAL") && !minLevel.equals("ERROR") && !minLevel.equals("WARN") &&
494                    !minLevel.equals("INFO") && !minLevel.equals("DEBUG") && !minLevel.equals("TRACE")) {
495                throw new IllegalArgumentException("Not a valid log level");
496            }
497            // Check that the text pattern is valid
498            Pattern textPattern = null;
499            try {
500                textPattern = text == null || text.equals("") ? null : Pattern.compile(text);
501            } catch (PatternSyntaxException e) {
502                throw new IllegalArgumentException("Bad regular expression '"+text+"'", e);
503            }
504            // Make sure we can find the log file
505            File log = new File(substituteSystemProps(logFile));
506            if(!log.exists()) {
507                throw new IllegalArgumentException("Log file "+log.getAbsolutePath()+" does not exist");
508            }
509            // Run the search
510            return searchFile(log, minLevel, textPattern, firstLine, lastLine, maxResults, includeStackTraces);
511        }
512    
513        /**
514         * Force the logging system to reconfigure.
515         */
516        public void reconfigure() {
517            File file = resolveConfigurationFile();
518            if (file == null || !file.exists()) {
519                return;
520            } else {
521                log.debug("reconfigure() using configurationFile=" + configurationFile);
522                lastChanged = file.lastModified();
523            }
524    
525            // Record the default console log level
526            System.setProperty("org.apache.geronimo.log.ConsoleLogLevel", GeronimoLogging.getConsoleLogLevel().toString());
527    
528            try {
529                URLConfigurator.configure(file.toURL());
530            } catch (MalformedURLException e) {
531                e.printStackTrace();
532            }
533    
534            // refresh the level info for every log
535            GeronimoLogFactory logFactory = (GeronimoLogFactory) LogFactory.getFactory();
536            Set instances = logFactory.getInstances();
537            for (Iterator iterator = instances.iterator(); iterator.hasNext();) {
538                Object log = iterator.next();
539                if (log instanceof CachingLog4jLog) {
540                    ((CachingLog4jLog)log).updateLevelInfo();
541                }
542            }
543        }
544    
545        private synchronized void schedule() {
546            if (timer != null) {
547                // kill the old monitor
548                if (monitor != null) {
549                    monitor.cancel();
550                }
551    
552                // start the new one
553                monitor = new URLMonitorTask();
554                TimerTask task = monitor;
555                timer.schedule(monitor, 1000 * refreshPeriod, 1000 * refreshPeriod);
556                task.run();
557            }
558        }
559    
560        public void doStart() {
561            LogFactory logFactory = LogFactory.getFactory();
562            if (logFactory instanceof GeronimoLogFactory) {
563                // Make sure the root Logger has loaded
564                Logger logger = LogManager.getRootLogger();
565    
566                // Change all of the loggers over to use log4j
567                GeronimoLogFactory geronimoLogFactory = (GeronimoLogFactory) logFactory;
568                synchronized (geronimoLogFactory) {
569                    if (!(geronimoLogFactory.getLogFactory() instanceof CachingLog4jLogFactory)) {
570                        geronimoLogFactory.setLogFactory(new CachingLog4jLogFactory());
571                    }
572                }
573    
574                synchronized (this) {
575                    reconfigure();
576    
577                    timer = new Timer(true);
578    
579                    // Periodically check the configuration file
580                    schedule();
581                }
582    
583                logEnvInfo();
584            }
585    
586            synchronized (this) {
587                running = true;
588            }
589        }
590    
591        public synchronized void doStop() {
592            running = false;
593            if (monitor != null) {
594                monitor.cancel();
595                monitor = null;
596            }
597            if (timer != null) {
598                timer.cancel();
599                timer = null;
600            }
601        }
602    
603        public void doFail() {
604            doStop();
605        }
606    
607        private synchronized File resolveConfigurationFile() {
608            try {
609                return serverInfo.resolveServer(configurationFile);
610            } catch (Exception e) {
611                return null;
612            }
613        }
614    
615        private void logEnvInfo() {
616           try {
617              Log log = LogFactory.getLog(Log4jService.class);
618              log.info("----------------------------------------------");
619              log.info("Started Logging Service");
620              log.debug("Log4jService created with configFileName=" + this.configurationFile +
621                        ", refreshPeriodSeconds=" + this.refreshPeriod);
622              log.info("Runtime Information:");
623              log.info("  Install Directory = " + DirectoryUtils.getGeronimoInstallDirectory().toString());
624              log.info("  JVM in use = " + System.getProperty("java.vendor") + " Java " + System.getProperty("java.version"));
625              log.info("Java Information:");
626              log.info("  System property [java.runtime.name]  = " + System.getProperty("java.runtime.name"));
627              log.info("  System property [java.runtime.version]  = " + System.getProperty("java.runtime.version"));
628              log.info("  System property [os.name]             = " + System.getProperty("os.name"));
629              log.info("  System property [os.version]          = " + System.getProperty("os.version"));
630              log.info("  System property [sun.os.patch.level]  = " + System.getProperty("sun.os.patch.level"));
631              log.info("  System property [os.arch]             = " + System.getProperty("os.arch"));
632              log.info("  System property [java.class.version]  = " + System.getProperty("java.class.version"));
633              log.info("  System property [locale]              = " + System.getProperty("user.language") + "_" + System.getProperty("user.country"));
634              log.info("  System property [unicode.encoding]    = " + System.getProperty("sun.io.unicode.encoding"));
635              log.info("  System property [file.encoding]       = " + System.getProperty("file.encoding"));
636              log.info("  System property [java.vm.name]        = " + System.getProperty("java.vm.name"));
637              log.info("  System property [java.vm.vendor]      = " + System.getProperty("java.vm.vendor"));
638              log.info("  System property [java.vm.version]     = " + System.getProperty("java.vm.version"));
639              log.info("  System property [java.vm.info]        = " + System.getProperty("java.vm.info"));
640              log.info("  System property [java.home]           = " + System.getProperty("java.home"));
641              log.info("  System property [java.classpath]      = " + System.getProperty("java.classpath"));
642              log.info("  System property [java.library.path]   = " + System.getProperty("java.library.path"));
643              log.info("  System property [java.endorsed.dirs]  = " + System.getProperty("java.endorsed.dirs"));
644              log.info("  System property [java.ext.dirs]       = " + System.getProperty("java.ext.dirs"));
645              log.info("  System property [sun.boot.class.path] = " + System.getProperty("sun.boot.class.path"));
646              log.info("----------------------------------------------");
647           } catch (Exception e) {
648              System.err.println("Exception caught during logging of Runtime Information.  Exception=" + e.toString());
649           }
650        }
651    
652    
653        private class URLMonitorTask extends TimerTask {
654            public void run() {
655                try {
656                    long lastModified;
657                    synchronized (this) {
658                        if (running == false) {
659                            return;
660                        }
661    
662                        File file = resolveConfigurationFile();
663                        if (file == null) {
664                            return;
665                        }
666    
667                        lastModified = file.lastModified();
668                    }
669    
670                    if (lastChanged < lastModified) {
671                        lastChanged = lastModified;
672                        reconfigure();
673                    }
674                } catch (Exception e) {
675                }
676            }
677        }
678    
679        private static class CachingLog4jLogFactory extends LogFactory {
680            public Log getInstance(Class clazz) throws LogConfigurationException {
681                return getInstance(clazz.getName());
682            }
683    
684            public Log getInstance(String name) throws LogConfigurationException {
685                return new CachingLog4jLog(name);
686            }
687    
688            public Object getAttribute(String name) {
689                return null;
690            }
691    
692            public String[] getAttributeNames() {
693                return new String[0];
694            }
695    
696            public void release() {
697            }
698    
699            public void removeAttribute(String name) {
700            }
701    
702            public void setAttribute(String name, Object value) {
703            }
704        }
705    
706        public static final GBeanInfo GBEAN_INFO;
707    
708        static {
709            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(Log4jService.class, "SystemLog");
710    
711            infoFactory.addAttribute("configFileName", String.class, true);
712            infoFactory.addAttribute("refreshPeriodSeconds", int.class, true);
713            infoFactory.addAttribute("configuration", String.class, false);
714            infoFactory.addAttribute("rootLoggerLevel", String.class, false);
715    
716            infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
717    
718            infoFactory.addOperation("reconfigure");
719            infoFactory.addOperation("setLoggerLevel", new Class[]{String.class, String.class});
720            infoFactory.addOperation("getLoggerLevel", new Class[]{String.class});
721            infoFactory.addOperation("getLoggerEffectiveLevel", new Class[]{String.class});
722            infoFactory.addInterface(SystemLog.class);
723    
724            infoFactory.setConstructor(new String[]{"configFileName", "refreshPeriodSeconds", "ServerInfo"});
725    
726            GBEAN_INFO = infoFactory.getBeanInfo();
727        }
728    
729        public static GBeanInfo getGBeanInfo() {
730            return GBEAN_INFO;
731        }
732    }