// 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"); if (logbuf == null) { throw new BadRequestException( "No CyclicBufferAppender found. Please configure logback " + "to store the latest log entries."); } } 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(); } } }