Skip to content
Advertisement

Logging access to Java servlet

I’m currently developing a Java Web application using Servlets. What I need to do is log to a file every access made to the website. To do that, I used Filters. So far, I’ve made it to the point where I can print everything to the console.

What I now need to do is store that into a file with a maximum of 10.000 entries up to 30 days old (if the maximum entries is achieved, the oldest ones are replaced when a new one is written).

How can I do that?

P.S: I cannot use a database for this assignment

Edit: I am not using a web framework. I can use logging frameworks.

Advertisement

Answer

So, this question actually prompted me to investigate whether any of the popular logging frameworks can actually do the task as requested.

While most do rolling logs based on file size and date/time, none of them had an easy way to do a rolling log based on entries in the log file. Also, existing logging frameworks typically store each day (and sometimes smaller units of time) in their own separate file, making for efficient cleanup based on date/time.

With a requirement for a maximum number of lines inside a single file, this necessitates reading the entire file into memory (very inefficient!). When everything, past and present is being written to a single file, removing older entries requires parsing each line for the date/time that entry was written (also, inefficient!).

Below is a simple program to demonstrate that this can be done, but there are some serious problems with this approach:

  • Not thread safe (if two threads try to read/write an entry simultaneously, one will be clobbered and the message will be skipped)
  • Slurping is bad (ten thousand entries is a lot: can the server slurp all that into memory?)

This is probably suitable for a toy project, a demonstration, or a school assignment.

This is NOT suitable for production applications, or really anything on the web that more than one person is going to use at a time.


In short, if you try to use a handi-craft program that you found on the internet for a mission-critical application that other people depend on, you are going to get exactly what you deserve.


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);
    }
}
Advertisement