/* * Copyright 2012 Jason Miller * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jj.logging; import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import javax.inject.Inject; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.MDC; import jj.ServerStarting; import jj.event.Listener; import jj.event.Subscriber; import jj.execution.ServerTask; import jj.execution.TaskRunner; import jj.util.Closer; /** * Coordinates asynchronous logging with the logging configuration system. Also provides * the implementation of the EmergencyLog for now, but that may change and/or go away * * @author jason * */ @Singleton @Subscriber class SystemLogger { static final String THREAD_NAME = "thread"; private final BlockingQueue<LoggedEvent> loggedEvents = new LinkedBlockingQueue<>(); private final TaskRunner taskRunner; private final Loggers loggers; private volatile boolean useQueue = true; @Inject SystemLogger(final TaskRunner taskRunner, final Loggers loggers) { this.taskRunner = taskRunner; this.loggers = loggers; } @Listener void on(ServerStarting serverStarting) { // just start immediately! we need to work quickly // block startup! final CountDownLatch startupLatch = new CountDownLatch(1); taskRunner.execute(new ServerTask("System Logger") { @Override protected void run() throws Exception { startupLatch.countDown(); try { for (;;) { LoggedEvent logged = loggedEvents.take(); doLog(logged); } } catch (InterruptedException ie) { // this is a shutdown, empty the waiting logs useQueue = false; loggedEvents.forEach(SystemLogger.this::doLog); throw ie; } catch (Exception e) { throw new AssertionError("logging threw!", e); } } }); // hacky signal from the test, // if the event is null then we don't really wait because it's not going // to start running at all, but if there is an event (hence a real server // of some sort) then we give it a quarter second to get running and bail // on the init if (serverStarting != null) { try { boolean started = startupLatch.await(250, MILLISECONDS); assert started : "FATAL\n" + "Could not start the system logger!\n" + "Check out jj.execution.TaskRunnerImpl, something is probably wrong there."; } catch (InterruptedException e) { throw new AssertionError(e); } } } private void doLog(LoggedEvent logged) { Logger logger = loggers.findLogger(logged); try (Closer closer = threadName(logged.threadName)) { logged.describeTo(logger); } } private void cleanThreadName() { MDC.remove(THREAD_NAME); } // package private because it is exposed in a test class Closer threadName(String threadName) { MDC.put(THREAD_NAME, threadName); return this::cleanThreadName; } /** * The actual main interface to the logging system, not intended for * direct use. Publish some correctly annotated descendent of * LoggedEvent instead. */ @Listener void on(LoggedEvent logged) { if (useQueue) { loggedEvents.add(logged); } else { doLog(logged); } } }