首页 > 解决方案 > 记录对 Java servlet 的访问

问题描述

我目前正在使用 Servlet 开发 Java Web 应用程序。我需要做的是每次访问网站时都记录到一个文件中。为此,我使用了过滤器。到目前为止,我已经可以将所有内容打印到控制台。

我现在需要做的是将其存储到一个文件中,该文件最多包含 10.000 个条目,最长可达 30 天(如果达到最大条目,则在写入新条目时替换最旧的条目)。

我怎样才能做到这一点?

PS:我不能为这个作业使用数据库

编辑:我没有使用网络框架。我可以使用日志框架。

标签: javalogging

解决方案


因此,这个问题实际上促使我调查是否有任何流行的日志记录框架实际上可以按要求完成任务。

虽然大多数基于文件大小和日期/时间滚动日志,但没有一个简单的方法可以根据日志文件中的条目来滚动日志。此外,现有的日志框架通常将每天(有时是较小的时间单位)存储在自己的单独文件中,从而根据日期/时间进行有效清理。

由于需要单个文件中的最大行数,因此需要将整个文件读入内存(非常低效!)。当过去和现在的所有内容都写入单个文件时,删除较旧的条目需要解析每一行以获取写入条目的日期/时间(同样,效率低下!)。

下面是一个简单的程序来证明可以做到这一点,但是这种方法存在一些严重的问题:

  • 不是线程安全的(如果两个线程试图同时读/写一个条目,一个会被破坏并且消息会被跳过)
  • 啜饮是不好的(一万个条目很多:服务器可以把所有这些啜饮到内存中吗?)

这可能适用于玩具项目、演示或学校作业。

这不适用于生产应用程序,或者实际上不适合多人同时使用的网络上的任何东西。


简而言之,如果您尝试将在 Internet 上找到的手工程序用于其他人所依赖的关键任务应用程序,您将得到您应得的。


public static void main(final String[] args) throws Exception
{
    final File logFile = new File("C:/", "EverythingInOneBigGiant.log");
    final int maxDays = 30;
    final int maxEntries = 10000;

    while (true)
    {
        // Just log the current time for this example, also makes parsing real simple
        final String msg = Instant.now().toString();
        slurpAndParse(logFile, msg, maxDays, maxEntries);

        // Wait a moment, before writing another entry
        Thread.sleep(750);
    }
}

private static void slurpAndParse(final File f, final String msg, final int maxDays, final int maxEntries)
        throws Exception
{
    // Slurp entire file into this buffer (possibly very large!)
    // Could crash your server if you run out of memory
    final StringBuffer sb = new StringBuffer();

    if (f.exists() && f.isFile())
    {
        final LocalDateTime now = LocalDateTime.now();

        final long totalLineCount = Files.lines(Paths.get(f.getAbsolutePath())).count();
        final long startAtLine = (totalLineCount < maxEntries ? 0 : (totalLineCount - maxEntries) + 1);
        long currentLineCount = 0;

        try (final BufferedReader br = new BufferedReader(new FileReader(f)))
        {
            String line;
            while (null != (line = br.readLine()))
            {
                // Ignore all lines before the start counter
                if (currentLineCount < startAtLine)
                {
                    ++currentLineCount;
                    continue;
                }

                // Parsing log data... while writing to the same log... ugh... how hideous
                final LocalDateTime lineDate = LocalDateTime.parse(line, DateTimeFormatter.ISO_ZONED_DATE_TIME);
                final Duration timeBetween = Duration.between(lineDate, now);
                // ... or maybe just use Math.abs() here? I defer to the date/time buffs
                final long dayDiff = (timeBetween.isNegative() ? timeBetween.negated() : timeBetween).toDays();

                // Only accept lines less than the max age in days
                if (dayDiff <= maxDays)
                {
                    sb.append(line);
                    sb.append(System.lineSeparator());
                }
            }
        }
    }

    System.out.println(msg);

    // Add the new log entry
    sb.append(msg);
    sb.append(System.lineSeparator());

    writeLog(f, sb.toString());
}

private static void writeLog(final File f, final String content) throws IOException
{
    try (final Writer out = new FileWriter(f))
    {
        out.write(content);
    }
}

推荐阅读