001/*
002 * #%L
003 * Netarchivesuite - monitor
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.monitor.logging;
025
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collections;
029import java.util.Iterator;
030import java.util.List;
031
032import ch.qos.logback.classic.Level;
033import ch.qos.logback.classic.PatternLayout;
034import ch.qos.logback.classic.spi.ILoggingEvent;
035import ch.qos.logback.core.AppenderBase;
036import ch.qos.logback.core.Context;
037import dk.netarkivet.common.exceptions.ArgumentNotValid;
038import dk.netarkivet.common.utils.Settings;
039import dk.netarkivet.monitor.MonitorSettings;
040
041/**
042 * SLF4J appender that caches a certain number of log entries in a cyclic manor.
043 * "DEBUG and TRACE entries are excluded".
044 */
045public class CachingSLF4JAppender extends AppenderBase<ILoggingEvent> {
046
047        /** Log format string pattern. */
048    protected String pattern;
049
050    /** Pattern layouter used to format log string. */
051    protected PatternLayout layout;
052
053    /** The size of the logging cache. */
054    protected final int loggingHistorySize;
055
056    /** The logging cache itself, caching the last "loggingHistorySize" log entries. */
057    protected final List<String> loggingHistory;
058
059    /** The log entries exposed as MBeans. */
060    protected final List<CachingSLF4JLogRecord> loggingMBeans;
061
062    /** The place in the loggingHistory for the next LogRecord. */
063    protected int currentIndex;
064
065    /**
066     * Initialize an instance of this class.
067     */
068    public CachingSLF4JAppender() {
069        layout = new PatternLayout();
070        loggingHistorySize = Settings.getInt(MonitorSettings.LOGGING_HISTORY_SIZE);
071        loggingHistory = Collections.synchronizedList(new ArrayList<String>(loggingHistorySize));
072        // Fill out the list with loggingHistorySize null-records.
073        loggingHistory.addAll(Arrays.asList(new String[loggingHistorySize]));
074        loggingMBeans = new ArrayList<CachingSLF4JLogRecord>(loggingHistorySize);
075        for (int i = 0; i < loggingHistorySize; i++) {
076            loggingMBeans.add(new CachingSLF4JLogRecord(i, this));
077        }
078        currentIndex = 0;
079    }
080
081    /**
082     * Returns the pattern used to format the log string.
083     * @return the pattern used to format the log string
084     */
085    public String getPattern() {
086        return pattern;
087    }
088
089    /**
090     * Set the pattern used to format the log string.
091     * The method should be called before the setContext() or start() methods, most notably if used programmatically..
092     * @param pattern log pattern
093     */
094    public void setPattern(String pattern) {
095        this.isStarted();
096        this.pattern = pattern;
097        layout.setPattern(pattern);
098    }
099
100    @Override
101    public void setContext(Context context) {
102        super.setContext(context);
103        layout.setContext(this.context);
104    }
105
106    @Override
107    public void start() {
108        super.start();
109        layout.start();
110    }
111
112    @Override
113    public void stop() {
114        super.stop();
115        layout.stop();
116    }
117
118    /**
119     * Close the appender and release associated resources.
120     */
121    public void close() {
122        layout = null;
123        loggingHistory.clear();
124        if (!loggingMBeans.isEmpty()) {
125                Iterator<CachingSLF4JLogRecord> iter = loggingMBeans.iterator();
126                while (iter.hasNext()) {
127                        iter.next().close();
128                }
129                loggingMBeans.clear();
130        }
131    }
132
133    @Override
134    protected void append(ILoggingEvent event) {
135        switch (event.getLevel().toInt()) {
136        case Level.TRACE_INT:
137        case Level.DEBUG_INT:
138                break;
139        case Level.INFO_INT:
140        case Level.WARN_INT:
141        case Level.ERROR_INT:
142                default:
143                loggingHistory.set(currentIndex, layout.doLayout(event));
144                currentIndex = (currentIndex + 1) % loggingHistorySize;
145                        break;
146        }
147    }
148
149    /**
150     * Returns the nth logrecord from the top.
151     *
152     * @param n The number of the log record to get
153     * @return The LogRecord which is number n from the top, or null for none.
154     */
155    public String getNthLogRecord(int n) {
156        if ((n < 0) || (n >= loggingHistorySize)) {
157            throw new ArgumentNotValid("Argument 'int n' must be between 0 and " + loggingHistorySize + ", but was "
158                    + n + ".");
159        }
160        return loggingHistory.get((currentIndex - n - 1 + loggingHistorySize) % loggingHistorySize);
161    }
162
163}