PageRenderTime 17ms CodeModel.GetById 1ms app.highlight 12ms RepoModel.GetById 2ms app.codeStats 0ms

/hudson-core/src/main/java/hudson/logging/LogRecorder.java

http://github.com/hudson/hudson
Java | 261 lines | 152 code | 33 blank | 76 comment | 15 complexity | 26742b1ab6ff2f3e860ce62bd49311d2 MD5 | raw file
  1/*
  2 * The MIT License
  3 * 
  4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
  5 * 
  6 * Permission is hereby granted, free of charge, to any person obtaining a copy
  7 * of this software and associated documentation files (the "Software"), to deal
  8 * in the Software without restriction, including without limitation the rights
  9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10 * copies of the Software, and to permit persons to whom the Software is
 11 * furnished to do so, subject to the following conditions:
 12 * 
 13 * The above copyright notice and this permission notice shall be included in
 14 * all copies or substantial portions of the Software.
 15 * 
 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 22 * THE SOFTWARE.
 23 */
 24package hudson.logging;
 25
 26import com.thoughtworks.xstream.XStream;
 27import hudson.BulkChange;
 28import hudson.Util;
 29import hudson.XmlFile;
 30import hudson.model.AbstractModelObject;
 31import hudson.model.Hudson;
 32import hudson.model.Saveable;
 33import hudson.model.listeners.SaveableListener;
 34import hudson.util.CopyOnWriteList;
 35import hudson.util.RingBufferLogHandler;
 36import hudson.util.XStream2;
 37import net.sf.json.JSONObject;
 38import org.kohsuke.stapler.DataBoundConstructor;
 39import org.kohsuke.stapler.StaplerRequest;
 40import org.kohsuke.stapler.StaplerResponse;
 41
 42import javax.servlet.ServletException;
 43import java.io.File;
 44import java.io.IOException;
 45import java.util.List;
 46import java.util.Arrays;
 47import java.util.Locale;
 48import java.util.logging.Level;
 49import java.util.logging.LogRecord;
 50import java.util.logging.Logger;
 51
 52/**
 53 * Records a selected set of logs so that the system administrator
 54 * can diagnose a specific aspect of the system.
 55 *
 56 * TODO: still a work in progress.
 57 *
 58 * <h3>Access Control</h3>
 59 * {@link LogRecorder} is only visible for administrators, and this access control happens at
 60 * {@link Hudson#getLog()}, the sole entry point for binding {@link LogRecorder} to URL.
 61 *
 62 * @author Kohsuke Kawaguchi
 63 * @see LogRecorderManager
 64 */
 65public class LogRecorder extends AbstractModelObject implements Saveable {
 66    private volatile String name;
 67
 68    //TODO: review and check whether we can do it private
 69    public final CopyOnWriteList<Target> targets = new CopyOnWriteList<Target>();
 70
 71    private transient /*almost final*/ RingBufferLogHandler handler = new RingBufferLogHandler() {
 72        @Override
 73        public void publish(LogRecord record) {
 74            for (Target t : targets) {
 75                if(t.includes(record)) {
 76                    super.publish(record);
 77                    return;
 78                }
 79            }
 80        }
 81    };
 82
 83    public CopyOnWriteList<Target> getTargets() {
 84        return targets;
 85    }
 86
 87    /**
 88     * Logger that this recorder monitors, and its log level.
 89     * Just a pair of (logger name,level) with convenience methods.
 90     */
 91    public static final class Target {
 92        public final String name;
 93        private final int level;
 94
 95        public Target(String name, Level level) {
 96            this(name,level.intValue());
 97        }
 98
 99        public Target(String name, int level) {
100            this.name = name;
101            this.level = level;
102        }
103
104        @DataBoundConstructor
105        public Target(String name, String level) {
106            this(name,Level.parse(level.toUpperCase(Locale.ENGLISH)));
107        }
108
109        public Level getLevel() {
110            return Level.parse(String.valueOf(level));
111        }
112
113        public boolean includes(LogRecord r) {
114            if(r.getLevel().intValue() < level)
115                return false;   // below the threshold
116            String logName = r.getLoggerName();
117            if(logName==null || !logName.startsWith(name))
118                return false;   // not within this logger
119
120            String rest = r.getLoggerName().substring(name.length());
121            return rest.startsWith(".") || rest.length()==0;
122        }
123
124        public Logger getLogger() {
125            return Logger.getLogger(name);
126        }
127
128        /**
129         * Makes sure that the logger passes through messages at the correct level to us.
130         */
131        public void enable() {
132            Logger l = getLogger();
133            if(!l.isLoggable(getLevel()))
134                l.setLevel(getLevel());
135        }
136    }
137
138    public LogRecorder(String name) {
139        this.name = name;
140        // register it only once when constructed, and when this object dies
141        // WeakLogHandler will remove it
142        new WeakLogHandler(handler,Logger.getLogger(""));
143    }
144
145    public String getDisplayName() {
146        return name;
147    }
148
149    public String getSearchUrl() {
150        return name;
151    }
152
153    public String getName() {
154        return name;
155    }
156
157    public LogRecorderManager getParent() {
158        return Hudson.getInstance().getLog();
159    }
160
161    /**
162     * Accepts submission from the configuration page.
163     */
164    public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
165        JSONObject src = req.getSubmittedForm();
166
167        String newName = src.getString("name"), redirect = ".";
168        XmlFile oldFile = null;
169        if(!name.equals(newName)) {
170            Hudson.checkGoodName(newName);
171            oldFile = getConfigFile();
172            // rename
173            getParent().logRecorders.remove(name);
174            this.name = newName;
175            getParent().logRecorders.put(name,this);
176            redirect = "../" + Util.rawEncode(newName) + '/';
177        }
178
179        List<Target> newTargets = req.bindJSONToList(Target.class, src.get("targets"));
180        for (Target t : newTargets)
181            t.enable();
182        targets.replaceBy(newTargets);
183
184        save();
185        if (oldFile!=null) oldFile.delete();
186        rsp.sendRedirect2(redirect);
187    }
188
189    /**
190     * Loads the settings from a file.
191     */
192    public synchronized void load() throws IOException {
193        getConfigFile().unmarshal(this);
194        for (Target t : targets)
195            t.enable();
196    }
197
198    /**
199     * Save the settings to a file.
200     */
201    public synchronized void save() throws IOException {
202        if(BulkChange.contains(this))   return;
203        getConfigFile().write(this);
204        SaveableListener.fireOnChange(this, getConfigFile());
205    }
206
207    /**
208     * Deletes this recorder, then go back to the parent.
209     */
210    public synchronized void doDoDelete(StaplerResponse rsp) throws IOException, ServletException {
211        requirePOST();
212        getConfigFile().delete();
213        getParent().logRecorders.remove(name);
214        // Disable logging for all our targets,
215        // then reenable all other loggers in case any also log the same targets
216        for (Target t : targets)
217            t.getLogger().setLevel(null);
218        for (LogRecorder log : getParent().logRecorders.values())
219            for (Target t : log.targets)
220                t.enable();
221        rsp.sendRedirect2("..");
222    }
223
224    /**
225     * RSS feed for log entries.
226     */
227    public void doRss( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
228        LogRecorderManager.doRss(req,rsp,getDisplayName(),getLogRecords());
229    }
230
231    /**
232     * The file we save our configuration.
233     */
234    private XmlFile getConfigFile() {
235        return new XmlFile(XSTREAM, new File(Hudson.getInstance().getRootDir(),"log/"+name+".xml"));
236    }
237
238    /**
239     * Gets a view of the log records.
240     */
241    public List<LogRecord> getLogRecords() {
242        return handler.getView();
243    }
244
245    /**
246     * Thread-safe reusable {@link XStream}.
247     */
248    public static final XStream XSTREAM = new XStream2();
249
250    static {
251        XSTREAM.alias("log",LogRecorder.class);
252        XSTREAM.alias("target",Target.class);
253    }
254
255    /**
256     * Log levels that can be configured for {@link Target}.
257     */
258    public static List<Level> LEVELS =
259            Arrays.asList(Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG,
260                    Level.FINE, Level.FINER, Level.FINEST, Level.ALL);
261}