在上一个博客中 ,我开始谈论需要弄清楚您的应用程序在生产环境中是否行为异常。 我说过,监视应用程序的一种方法是检查其日志文件是否存在异常,如果发现异常,则采取适当的措施。 显然,日志文件会占用数百兆的磁盘空间,而手动监视它们是不切实际的,而且很无聊。
我还说过,有几种方法可以自动监视日志文件,并提出了一个基于Spring的实用程序,该实用程序每天都会梳理日志文件,并在发现任何异常时向您发送电子邮件。
我只介绍了第一类: FileLocator
,它将在目录及其子目录中搜索日志文件。 找到一个后,将其传递给FileValidator
。
FileValidator
必须对文件执行多项检查。 首先,它必须确定文件是否足够年轻以检查异常。 想法是,由于应用程序定期运行,因此没有必要检查目录中找到的所有文件是否存在错误,我们只希望自应用程序上次运行以来已创建或更新的文件。
这种设计的思想是将同一接口的几种实现组合在一起,创建一个负责验证文件的聚合对象。 鹰眼的读者会注意到,这是Delegate Pattern的实现。
在上面的类图中,将RegexValidator
和FileAgeValidator
实例注入到FileValidator
,并将其验证任务委托给这些类。
依次进行这些操作,并首先处理Validator
接口…
public interface Validator { /** The validation method */ public <T> boolean validate(T obj); }
上面的代码演示了Validator
接口的简单性。 它具有单个方法validate(T obj)
,这是一个泛型方法调用,可以提高此接口的灵活性和可重用性。 当类实现此接口时,它们可以更改输入参数类型以适合自己的目的……如下面的第一个实现所示:
public class RegexValidator implements Validator { private static final Logger logger = LoggerFactory.getLogger(RegexValidator.class); private final Pattern pattern; public RegexValidator(String regex) { pattern = Pattern.compile(regex); logger.info("loaded regex: {}", regex); } @Override public <T> boolean validate(T string) { boolean retVal = false; Matcher matcher = pattern.matcher((String) string); retVal = matcher.matches(); if (retVal && logger.isDebugEnabled()) { logger.debug("Found error line: {}", string); } return retVal; }
}
RegexValidator
类具有一个带正则表达式字符串的单个参数构造函数。 然后将其转换为Pattern
实例变量,并由validate(T string)
方法使用它来测试String
输入参数是否与原始构造函数arg正则表达式匹配。 如果是这样,那么validate(T string)
将返回true。
@Service
public class FileAgeValidator implements Validator { @Value("${max.days}") private int maxDays; /** * Validate the age of the file. * * @see com.captaindebug.errortrack.Validator#validate(java.lang.Object) */ @Override public <T> boolean validate(T obj) { File file = (File) obj; Calendar fileDate = getFileDate(file); Calendar ageLimit = getFileAgeLimit(); boolean retVal = false; if (fileDate.after(ageLimit)) { retVal = true; } return retVal; } private Calendar getFileAgeLimit() { Calendar cal = Calendar.getInstance(); cal.add(Calendar.DAY_OF_MONTH, -1 * maxDays); return cal; } private Calendar getFileDate(File file) { long fileDate = file.lastModified(); Calendar when = Calendar.getInstance(); when.setTimeInMillis(fileDate); return when; } }
第二个Validator(T obj)
实现是FileAgeValidator
显示的FileAgeValidator
,首先要注意的是,整个过程是由max.days
属性驱动的。 这被注入到FileAgeValidator
的@Value
注释的maxDays
实例变量中。 此变量确定文件的最长使用期限(天)。 该文件早于该值,然后validate(T obj)
将返回false。
在此实现中, validate(T obj)
'obj'参数被强制转换为File
对象,然后将其用于将文件的日期转换为Calendar
对象。 下一行代码将maxDays
变量转换为第二个Calendar
对象: ageLimit
。 然后将ageLimit
与fileDate
对象进行比较。 如果fileDate
在ageLimit
之后,则validate(T obj)
返回true。
validator
程序包中的最后一个类是FileValidator
,如上所示,它将很多职责委托给其他三个聚合的验证程序:一个FileAgeValidator
和两个RegexValidator
。
@Service
public class FileValidator implements Validator { private static final Logger logger = LoggerFactory.getLogger(FileValidator.class); @Value("${following.lines}") private Integer extraLineCount; @Autowired @Qualifier("scan-for") private RegexValidator scanForValidator; @Autowired(required = false) @Qualifier("exclude") private RegexValidator excludeValidator; @Autowired private FileAgeValidator fileAgeValidator; @Autowired private Results results; @Override public <T> boolean validate(T obj) { boolean retVal = false; File file = (File) obj; if (fileAgeValidator.validate(file)) { results.addFile(file.getPath()); checkFile(file); retVal = true; } return retVal; } private void checkFile(File file) { try { BufferedReader in = createBufferedReader(file); readLines(in, file); in.close(); } catch (Exception e) { logger.error("Error whilst processing file: " + file.getPath() + " Message: " + e.getMessage(), e); } } @VisibleForTesting BufferedReader createBufferedReader(File file) throws FileNotFoundException { BufferedReader in = new BufferedReader(new FileReader(file)); return in; } private void readLines(BufferedReader in, File file) throws IOException { int lineNumber = 0; String line; do { line = in.readLine(); if (isNotNull(line)) { processLine(line, file.getPath(), ++lineNumber, in); } } while (isNotNull(line)); } private boolean isNotNull(Object obj) { return obj != null; } private int processLine(String line, String filePath, int lineNumber, BufferedReader in) throws IOException { if (canValidateLine(line) && scanForValidator.validate(line)) { List<String> lines = new ArrayList<String>(); lines.add(line); addExtraDetailLines(in, lines); results.addResult(filePath, lineNumber, lines); lineNumber += extraLineCount; } return lineNumber; } private boolean canValidateLine(String line) { boolean retVal = true; if (isNotNull(excludeValidator)) { retVal = !excludeValidator.validate(line); } return retVal; } private void addExtraDetailLines(BufferedReader in, List<String> lines) throws IOException { for (int i = 0; i < extraLineCount; i++) { String line = in.readLine(); if (isNotNull(line)) { lines.add(line); } else { break; } } } }
FileValidator
的validate(T obj)
将File
作为参数。 它的首要职责是验证文件的年龄。 如果该验证器返回true,则它将通知Report
类它已找到一个新的有效文件。 然后,它检查文件中是否有错误,并将找到的任何内容添加到Report
实例。 它通过使用BufferedReader
依次检查文件的每一行来做到这一点。 在检查某行是否包含错误之前,它会检查该行是否未从检查中排除-即,它与排除的异常或我们不感兴趣的异常不匹配。如果该行与排除的异常不匹配异常,然后使用RegexValidator
的第二个实例检查是否需要查找异常。 如果该行确实包含错误,则会将其添加到List<String>
对象。 然后从添加到列表的文件中读取以下几行,以使报告更具可读性。
因此,文件解析将继续,一次检查每一行以查找错误并建立报告,以便以后处理。
该封面的验证文件使用委托模式添加了发现到Report
任何异常,但是此Report
对象如何工作? 我没有提到它,输出是如何产生的? 下次再说。
- 该博客的代码可在Github上找到: https : //github.com/roghughe/captaindebug/tree/master/error-track 。
翻译自: https://www.javacodegeeks.com/2014/03/tracking-exceptions-with-spring-part-2-delegate-pattern.html