/* * The MIT License * * Copyright (c) 2010, dvrzalik, Stellar Science Ltd Co, K. R. Walker * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.plugins.emailext.plugins.content; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.plugins.emailext.EmailType; import hudson.plugins.emailext.ExtendedEmailPublisher; import hudson.plugins.emailext.plugins.EmailContent; import hudson.tasks.Mailer; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; /** * An EmailContent for build log lines matching a regular expression. * Shows lines matching a regular expression (with optional context lines) * from the build log file. * * @author krwalker@stellarscience.com */ public class BuildLogRegexContent implements EmailContent { private static final Logger LOGGER = Logger.getLogger(Mailer.class.getName()); private static final String TOKEN = "BUILD_LOG_REGEX"; private static final String REGEX_ARG_NAME = "regex"; private static final String REGEX_DEFAULT_VALUE = "(?i)\\b(error|exception|fatal|fail(ed|ure)|un(defined|resolved))\\b"; private static final String LINES_BEFORE_ARG_NAME = "linesBefore"; private static final int LINES_BEFORE_DEFAULT_VALUE = 0; private static final String LINES_AFTER_ARG_NAME = "linesAfter"; private static final int LINES_AFTER_DEFAULT_VALUE = 0; private static final String MAX_MATCHES_ARG_NAME = "maxMatches"; private static final int MAX_MATCHES_DEFAULT_VALUE = 0; private static final String SHOW_TRUNCATED_LINES_ARG_NAME = "showTruncatedLines"; private static final boolean SHOW_TRUNCATED_LINES_DEFAULT_VALUE = true; public String getToken() { return TOKEN; } public List<String> getArguments() { return Arrays.asList( REGEX_ARG_NAME, LINES_BEFORE_ARG_NAME, LINES_AFTER_ARG_NAME, MAX_MATCHES_ARG_NAME, SHOW_TRUNCATED_LINES_ARG_NAME); } public String getHelpText() { return "Displays lines from the build log that match the regular expression.\n" + "<ul\n" + "<li><i>" + REGEX_ARG_NAME + "</i> - Lines that match this regular expression " + "are included. See also <i>java.util.regex.Pattern</i><br>\n" + "Defaults to \"" + REGEX_DEFAULT_VALUE + "\".\n" + "<li><i>" + LINES_BEFORE_ARG_NAME + "</i> - The number of lines to include " + "before the matching line. Lines that overlap with another " + "match or <i>linesAfter</i> are only included once.<br>\n" + "Defaults to " + LINES_BEFORE_DEFAULT_VALUE + ".\n" + "<li><i>" + LINES_AFTER_ARG_NAME + "</i> - The number of lines to include " + "after the matching line. Lines that overlap with another " + "match or <i>linesBefore</i> are only included once.<br>\n" + "Defaults to " + LINES_AFTER_DEFAULT_VALUE + ".\n" + "<li><i>" + MAX_MATCHES_ARG_NAME + "</i> - The maximum number of matches " + "to include. If 0, all matches will be included.<br>\n" + "Defaults to " + MAX_MATCHES_DEFAULT_VALUE + ".\n" + "<li><i>" + SHOW_TRUNCATED_LINES_ARG_NAME + "</i> - If <i>true</i>, include " + "<tt>[...truncated ### lines...]</tt> lines.<br>\n" + "Defaults to " + SHOW_TRUNCATED_LINES_DEFAULT_VALUE + ".\n" + "</ul>\n"; } private void append(StringBuffer buffer, String line) { buffer.append(line); buffer.append('\n'); } private void appendLinesTruncated(StringBuffer buffer, int numLinesTruncated) { // This format comes from hudson.model.Run.getLog(maxLines). append(buffer, "[...truncated " + numLinesTruncated + " lines...]"); } public <P extends AbstractProject<P, B>, B extends AbstractBuild<P, B>> String getContent(AbstractBuild<P, B> build, ExtendedEmailPublisher publisher, EmailType emailType, Map<String, ?> args) { //LOGGER.log(Level.INFO, TOKEN + " getContent"); final String regex = Args.get(args, REGEX_ARG_NAME, REGEX_DEFAULT_VALUE); final int contextLinesBefore = Args.get(args, LINES_BEFORE_ARG_NAME, LINES_BEFORE_DEFAULT_VALUE); final int contextLinesAfter = Args.get(args, LINES_AFTER_ARG_NAME, LINES_AFTER_DEFAULT_VALUE); final int maxMatches = Args.get(args, MAX_MATCHES_ARG_NAME, MAX_MATCHES_DEFAULT_VALUE); final boolean showTruncatedLines = Args.get(args, SHOW_TRUNCATED_LINES_ARG_NAME, SHOW_TRUNCATED_LINES_DEFAULT_VALUE); final Pattern pattern = Pattern.compile(regex); final StringBuffer buffer = new StringBuffer(); try { final BufferedReader reader = new BufferedReader(new FileReader(build.getLogFile())); try { int numLinesTruncated = 0; int numMatches = 0; int numLinesStillNeeded = 0; Queue<String> linesBefore = new LinkedList<String>(); String line = null; while ((line = reader.readLine()) != null) { // Remove any lines before that are no longer needed. while (linesBefore.size() > contextLinesBefore) { linesBefore.remove(); ++numLinesTruncated; } if (pattern.matcher(line).find()) { // The current line matches. if (showTruncatedLines == true && numLinesTruncated > 0) { // Append information about truncated lines. appendLinesTruncated(buffer, numLinesTruncated); numLinesTruncated = 0; } // Append all the linesBefore. while (linesBefore.size() > 0) { append(buffer, linesBefore.remove()); } // Append the current line. append(buffer, line); ++numMatches; // Set up to add numLinesStillNeeded numLinesStillNeeded = contextLinesAfter; } else { // The current line did not match. if (numLinesStillNeeded > 0) { // Append this line as a line after. append(buffer, line); --numLinesStillNeeded; } else { // Store this line as a possible line before. linesBefore.offer(line); } } if (maxMatches != 0 && numMatches >= maxMatches && numLinesStillNeeded == 0) { break; } } if (showTruncatedLines == true) { // Count the rest of the lines. // Include any lines in linesBefore. while (linesBefore.size() > 0) { linesBefore.remove(); ++numLinesTruncated; } if (line != null) { // Include the rest of the lines that haven't been read in. while ((line = reader.readLine()) != null) { ++numLinesTruncated; } } if (numLinesTruncated > 0) { appendLinesTruncated(buffer, numLinesTruncated); } } } finally { reader.close(); } } catch (IOException ex) { LOGGER.log(Level.SEVERE, null, ex); } //LOGGER.log(Level.INFO, "${BUILD_LOG_REGEX,...}:\n" + buffer.toString()); return buffer.toString(); } public boolean hasNestedContent() { return false; } }