/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. */
  24. package hudson.logging;
  25. import com.thoughtworks.xstream.XStream;
  26. import hudson.BulkChange;
  27. import hudson.Util;
  28. import hudson.XmlFile;
  29. import hudson.model.AbstractModelObject;
  30. import hudson.model.Hudson;
  31. import hudson.model.Saveable;
  32. import hudson.model.listeners.SaveableListener;
  33. import hudson.util.CopyOnWriteList;
  34. import hudson.util.RingBufferLogHandler;
  35. import hudson.util.XStream2;
  36. import net.sf.json.JSONObject;
  37. import org.kohsuke.stapler.DataBoundConstructor;
  38. import org.kohsuke.stapler.StaplerRequest;
  39. import org.kohsuke.stapler.StaplerResponse;
  40. import javax.servlet.ServletException;
  41. import java.io.File;
  42. import java.io.IOException;
  43. import java.util.List;
  44. import java.util.Arrays;
  45. import java.util.Locale;
  46. import java.util.logging.Level;
  47. import java.util.logging.LogRecord;
  48. import java.util.logging.Logger;
  49. /**
  50. * Records a selected set of logs so that the system administrator
  51. * can diagnose a specific aspect of the system.
  52. *
  53. * TODO: still a work in progress.
  54. *
  55. * <h3>Access Control</h3>
  56. * {@link LogRecorder} is only visible for administrators, and this access control happens at
  57. * {@link Hudson#getLog()}, the sole entry point for binding {@link LogRecorder} to URL.
  58. *
  59. * @author Kohsuke Kawaguchi
  60. * @see LogRecorderManager
  61. */
  62. public class LogRecorder extends AbstractModelObject implements Saveable {
  63. private volatile String name;
  64. //TODO: review and check whether we can do it private
  65. public final CopyOnWriteList<Target> targets = new CopyOnWriteList<Target>();
  66. private transient /*almost final*/ RingBufferLogHandler handler = new RingBufferLogHandler() {
  67. @Override
  68. public void publish(LogRecord record) {
  69. for (Target t : targets) {
  70. if(t.includes(record)) {
  71. super.publish(record);
  72. return;
  73. }
  74. }
  75. }
  76. };
  77. public CopyOnWriteList<Target> getTargets() {
  78. return targets;
  79. }
  80. /**
  81. * Logger that this recorder monitors, and its log level.
  82. * Just a pair of (logger name,level) with convenience methods.
  83. */
  84. public static final class Target {
  85. public final String name;
  86. private final int level;
  87. public Target(String name, Level level) {
  88. this(name,level.intValue());
  89. }
  90. public Target(String name, int level) {
  91. this.name = name;
  92. this.level = level;
  93. }
  94. @DataBoundConstructor
  95. public Target(String name, String level) {
  96. this(name,Level.parse(level.toUpperCase(Locale.ENGLISH)));
  97. }
  98. public Level getLevel() {
  99. return Level.parse(String.valueOf(level));
  100. }
  101. public boolean includes(LogRecord r) {
  102. if(r.getLevel().intValue() < level)
  103. return false; // below the threshold
  104. String logName = r.getLoggerName();
  105. if(logName==null || !logName.startsWith(name))
  106. return false; // not within this logger
  107. String rest = r.getLoggerName().substring(name.length());
  108. return rest.startsWith(".") || rest.length()==0;
  109. }
  110. public Logger getLogger() {
  111. return Logger.getLogger(name);
  112. }
  113. /**
  114. * Makes sure that the logger passes through messages at the correct level to us.
  115. */
  116. public void enable() {
  117. Logger l = getLogger();
  118. if(!l.isLoggable(getLevel()))
  119. l.setLevel(getLevel());
  120. }
  121. }
  122. public LogRecorder(String name) {
  123. this.name = name;
  124. // register it only once when constructed, and when this object dies
  125. // WeakLogHandler will remove it
  126. new WeakLogHandler(handler,Logger.getLogger(""));
  127. }
  128. public String getDisplayName() {
  129. return name;
  130. }
  131. public String getSearchUrl() {
  132. return name;
  133. }
  134. public String getName() {
  135. return name;
  136. }
  137. public LogRecorderManager getParent() {
  138. return Hudson.getInstance().getLog();
  139. }
  140. /**
  141. * Accepts submission from the configuration page.
  142. */
  143. public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
  144. JSONObject src = req.getSubmittedForm();
  145. String newName = src.getString("name"), redirect = ".";
  146. XmlFile oldFile = null;
  147. if(!name.equals(newName)) {
  148. Hudson.checkGoodName(newName);
  149. oldFile = getConfigFile();
  150. // rename
  151. getParent().logRecorders.remove(name);
  152. this.name = newName;
  153. getParent().logRecorders.put(name,this);
  154. redirect = "../" + Util.rawEncode(newName) + '/';
  155. }
  156. List<Target> newTargets = req.bindJSONToList(Target.class, src.get("targets"));
  157. for (Target t : newTargets)
  158. t.enable();
  159. targets.replaceBy(newTargets);
  160. save();
  161. if (oldFile!=null) oldFile.delete();
  162. rsp.sendRedirect2(redirect);
  163. }
  164. /**
  165. * Loads the settings from a file.
  166. */
  167. public synchronized void load() throws IOException {
  168. getConfigFile().unmarshal(this);
  169. for (Target t : targets)
  170. t.enable();
  171. }
  172. /**
  173. * Save the settings to a file.
  174. */
  175. public synchronized void save() throws IOException {
  176. if(BulkChange.contains(this)) return;
  177. getConfigFile().write(this);
  178. SaveableListener.fireOnChange(this, getConfigFile());
  179. }
  180. /**
  181. * Deletes this recorder, then go back to the parent.
  182. */
  183. public synchronized void doDoDelete(StaplerResponse rsp) throws IOException, ServletException {
  184. requirePOST();
  185. getConfigFile().delete();
  186. getParent().logRecorders.remove(name);
  187. // Disable logging for all our targets,
  188. // then reenable all other loggers in case any also log the same targets
  189. for (Target t : targets)
  190. t.getLogger().setLevel(null);
  191. for (LogRecorder log : getParent().logRecorders.values())
  192. for (Target t : log.targets)
  193. t.enable();
  194. rsp.sendRedirect2("..");
  195. }
  196. /**
  197. * RSS feed for log entries.
  198. */
  199. public void doRss( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
  200. LogRecorderManager.doRss(req,rsp,getDisplayName(),getLogRecords());
  201. }
  202. /**
  203. * The file we save our configuration.
  204. */
  205. private XmlFile getConfigFile() {
  206. return new XmlFile(XSTREAM, new File(Hudson.getInstance().getRootDir(),"log/"+name+".xml"));
  207. }
  208. /**
  209. * Gets a view of the log records.
  210. */
  211. public List<LogRecord> getLogRecords() {
  212. return handler.getView();
  213. }
  214. /**
  215. * Thread-safe reusable {@link XStream}.
  216. */
  217. public static final XStream XSTREAM = new XStream2();
  218. static {
  219. XSTREAM.alias("log",LogRecorder.class);
  220. XSTREAM.alias("target",Target.class);
  221. }
  222. /**
  223. * Log levels that can be configured for {@link Target}.
  224. */
  225. public static List<Level> LEVELS =
  226. Arrays.asList(Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG,
  227. Level.FINE, Level.FINER, Level.FINEST, Level.ALL);
  228. }