// This file is part of OpenTSDB.
// Copyright (C) 2010-2012  The OpenTSDB Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or (at your
// option) any later version.  This program is distributed in the hope that it
// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
// General Public License for more details.  You should have received a copy
// of the GNU Lesser General Public License along with this program.  If not,
// see <http://www.gnu.org/licenses/>.
package net.opentsdb.tsd;

import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonGenerationException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.read.CyclicBufferAppender;

import net.opentsdb.core.TSDB;
import net.opentsdb.utils.JSON;

/** The "/logs" endpoint. */
final class LogsRpc implements HttpRpc {

  public void execute(final TSDB tsdb, final HttpQuery query) 
    throws JsonGenerationException, IOException {
    LogIterator logmsgs = new LogIterator();
    if (query.hasQueryStringParam("json")) {
      ArrayList<String> logs = new ArrayList<String>();
      for (String log : logmsgs) {
        logs.add(log);
      }
      query.sendReply(JSON.serializeToBytes(logs));
    } else if (query.hasQueryStringParam("level")) {
      final Level level = Level.toLevel(query.getQueryStringParam("level"),
                                        null);
      if (level == null) {
        throw new BadRequestException("Invalid level: "
                                      + query.getQueryStringParam("level"));
      }
      final Logger root =
        (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
      String logger_name = query.getQueryStringParam("logger");
      if (logger_name == null) {
        logger_name = Logger.ROOT_LOGGER_NAME;
      } else if (root.getLoggerContext().exists(logger_name) == null) {
        throw new BadRequestException("Invalid logger: " + logger_name);
      }
      final Logger logger = (Logger) LoggerFactory.getLogger(logger_name);
      int nloggers = 0;
      if (logger == root) {  // Update all the loggers.
        for (final Logger l : logger.getLoggerContext().getLoggerList()) {
          l.setLevel(level);
          nloggers++;
        }
      } else {
        logger.setLevel(level);
        nloggers++;
      }
      query.sendReply("Set the log level to " + level + " on " + nloggers
                      + " logger" + (nloggers > 1 ? "s" : "") + ".\n");
    } else {
      final StringBuilder buf = new StringBuilder(512);
      for (final String logmsg : logmsgs) {
        buf.append(logmsg).append('\n');
      }
      logmsgs = null;
      query.sendReply(buf);
    }
  }

  /** Helper class to iterate over logback's recent log messages. */
  private static final class LogIterator implements Iterator<String>,
                                                    Iterable<String> {

    private final CyclicBufferAppender<ILoggingEvent> logbuf;
    private final StringBuilder buf = new StringBuilder(64);
    private int nevents;

    public LogIterator() {
      final Logger root =
        (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
      logbuf = (CyclicBufferAppender<ILoggingEvent>) root.getAppender("CYCLIC");
    }

    public Iterator<String> iterator() {
      nevents = logbuf.getLength();
      return this;
    }

    public boolean hasNext() {
      return nevents > 0;
    }

    public String next() {
      if (hasNext()) {
        nevents--;
        final ILoggingEvent event = (ILoggingEvent) logbuf.get(nevents);
        final String msg = event.getFormattedMessage();
        buf.setLength(0);
        buf.append(event.getTimeStamp() / 1000)
          .append('\t').append(event.getLevel().toString())
          .append('\t').append(event.getThreadName())
          .append('\t').append(event.getLoggerName())
          .append('\t').append(msg);
        final IThrowableProxy thrown = event.getThrowableProxy();
        if (thrown != null) {
          buf.append('\t').append(ThrowableProxyUtil.asString(thrown));
        }
        return buf.toString();
      }
      throw new NoSuchElementException("no more elements");
    }

    public void remove() {
      throw new UnsupportedOperationException();
    }

  }

}

@@ -3809,24 +3809,221 @@
 (%22CYCLIC%22);%0A
+      if (logbuf == null) %7B%0A        throw new BadRequestException(%0A            %22No CyclicBufferAppender found. Please configure logback %22%0A            + %22to store the latest log entries.%22);%0A      %7D%0A
     %7D%0A%0A    p

Fix #679 where an NPE is thrown if the logback config doesn't have a cyclic buffered appender to keep track of the last x log lines. Now it will throw a BadRequest exception with a useful message.