001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.reef.util.logging;
020
021import org.apache.reef.javabridge.NativeInterop;
022
023import javax.inject.Inject;
024import java.util.ArrayList;
025import java.util.concurrent.ScheduledThreadPoolExecutor;
026import java.util.concurrent.TimeUnit;
027import java.util.logging.Handler;
028import java.util.logging.Level;
029import java.util.logging.LogRecord;
030import java.util.logging.SimpleFormatter;
031
032/**
033 * Logging Handler to intercept java logs and transfer them
034 * to the CLR side via the reef-bridge.
035 * <p>
036 * Logs are buffered to avoid the cost of reef-bridge function calls.
037 * A thread is also scheduled to flush the log buffer at a certain interval,
038 * in case the log buffer remains unfilled for an extended period of time.
039 */
040public class CLRBufferedLogHandler extends Handler {
041  private static final int BUFFER_LEN = 10;
042  private static final int NUM_THREADS = 1;
043  private static final long LOG_SCHEDULE_PERIOD = 15;  // seconds
044  private SimpleFormatter formatter;
045  private ArrayList<LogRecord> logs;
046  private boolean driverInitialized;
047  private ScheduledThreadPoolExecutor logScheduler;
048
049  @Inject
050  public CLRBufferedLogHandler() {
051    super();
052    this.formatter = new SimpleFormatter();
053    this.logs = new ArrayList<>();
054    this.driverInitialized = false;
055    this.logScheduler = new ScheduledThreadPoolExecutor(NUM_THREADS);
056  }
057
058  /**
059   * Signals the java-bridge has been initialized and that we can begin logging.
060   * Usually called from the StartHandler after the driver is up.
061   */
062  public void setDriverInitialized() {
063    synchronized (this) {
064      this.driverInitialized = true;
065    }
066    startLogScheduler();
067  }
068
069  /**
070   * Called whenever a log message is received on the java side.
071   * <p>
072   * Adds the log record to the log buffer. If the log buffer is full and
073   * the driver has already been initialized, flush the buffer of the logs.
074   */
075  @Override
076  public void publish(final LogRecord record) {
077    if (record == null) {
078      return;
079    }
080
081    if (!isLoggable(record)) {
082      return;
083    }
084
085    synchronized (this) {
086      this.logs.add(record);
087      if (!this.driverInitialized || this.logs.size() < BUFFER_LEN) {
088        return;
089      }
090    }
091
092    logAll();
093  }
094
095  @Override
096  public void flush() {
097    logAll();
098  }
099
100  /**
101   * Flushes the remaining buffered logs and shuts down the log scheduler thread.
102   */
103  @Override
104  public synchronized void close() throws SecurityException {
105    if (driverInitialized) {
106      this.logAll();
107    }
108    this.logScheduler.shutdown();
109  }
110
111  /**
112   * Starts a thread to flush the log buffer on an interval.
113   * <p>
114   * This will ensure that logs get flushed periodically, even
115   * if the log buffer is not full.
116   */
117  private void startLogScheduler() {
118    this.logScheduler.scheduleAtFixedRate(
119        new Runnable() {
120          @Override
121          public void run() {
122            CLRBufferedLogHandler.this.logAll();
123          }
124        }, 0, LOG_SCHEDULE_PERIOD, TimeUnit.SECONDS);
125  }
126
127  /**
128   * Flushes the log buffer, logging each buffered log message using
129   * the reef-bridge log function.
130   */
131  private void logAll() {
132    synchronized (this) {
133      final StringBuilder sb = new StringBuilder();
134      Level highestLevel = Level.FINEST;
135      for (final LogRecord record : this.logs) {
136        sb.append(formatter.format(record));
137        sb.append("\n");
138        if (record.getLevel().intValue() > highestLevel.intValue()) {
139          highestLevel = record.getLevel();
140        }
141      }
142      try {
143        final int level = getLevel(highestLevel);
144        NativeInterop.clrBufferedLog(level, sb.toString());
145      } catch (Exception e) {
146        System.err.println("Failed to perform CLRBufferedLogHandler");
147      }
148
149      this.logs.clear();
150    }
151  }
152
153  /**
154   * Returns the integer value of the log record's level to be used
155   * by the CLR Bridge log function.
156   */
157  private int getLevel(final Level recordLevel) {
158    if (recordLevel.equals(Level.OFF)) {
159      return 0;
160    } else if (recordLevel.equals(Level.SEVERE)) {
161      return 1;
162    } else if (recordLevel.equals(Level.WARNING)) {
163      return 2;
164    } else if (recordLevel.equals(Level.ALL)) {
165      return 4;
166    } else {
167      return 3;
168    }
169  }
170}