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}