001/* 002 * Copyright (C) 2019 Michael N. Lipp (http://www.mnl.de) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package de.mnl.osgi.jul2osgi; 018 019import de.mnl.osgi.coreutils.ServiceResolver; 020import de.mnl.osgi.jul2osgi.lib.LogManager; 021import de.mnl.osgi.jul2osgi.lib.LogManager.LogInfo; 022import de.mnl.osgi.jul2osgi.lib.LogRecordHandler; 023 024import java.lang.ref.WeakReference; 025import java.security.AccessController; 026import java.security.PrivilegedAction; 027import java.text.MessageFormat; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.Map; 031import java.util.MissingResourceException; 032import java.util.Optional; 033import java.util.Set; 034import java.util.WeakHashMap; 035import java.util.concurrent.ConcurrentHashMap; 036import java.util.logging.Level; 037import java.util.logging.LogRecord; 038 039import org.osgi.framework.Bundle; 040import org.osgi.framework.BundleContext; 041import org.osgi.framework.FrameworkUtil; 042import org.osgi.service.log.LogLevel; 043import org.osgi.service.log.LogService; 044import org.osgi.service.log.Logger; 045import org.osgi.service.log.admin.LoggerAdmin; 046import org.osgi.service.log.admin.LoggerContext; 047 048/** 049 */ 050@SuppressWarnings("PMD.DataflowAnomalyAnalysis") 051public class Forwarder extends ServiceResolver implements LogRecordHandler { 052 053 private String logPattern; 054 private boolean adaptOsgiLevel = true; 055 private final Map<Class<?>, WeakReference<Bundle>> bundles 056 = new ConcurrentHashMap<>(); 057 private final Set<Bundle> adaptedBundles = Collections 058 .synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); 059 060 @Override 061 @SuppressWarnings({ "PMD.SystemPrintln" }) 062 protected void configure() { 063 final java.util.logging.LogManager logMgr 064 = java.util.logging.LogManager.getLogManager(); 065 if (!(logMgr instanceof LogManager)) { 066 System.err.println("Configuration error: " 067 + "Bundle de.mnl.osgi.jul2osgi must be used with " 068 + "LogManager from de.mnl.osgi.jul2osgi.log."); 069 return; 070 } 071 logPattern = Optional.ofNullable( 072 context.getProperty("de.mnl.osgi.jul2osgi.logPattern")) 073 .orElse("{0}"); 074 String adaptProperty = context.getProperty( 075 "de.mnl.osgi.jul2osgi.adaptOSGiLevel"); 076 if (adaptProperty != null) { 077 adaptOsgiLevel = Boolean.parseBoolean(adaptProperty); 078 } 079 080 addDependency(LogService.class); 081 addDependency(LoggerAdmin.class); 082 setOnResolved(() -> { 083 if (adaptOsgiLevel) { 084 // Handle this bundle specially 085 adaptLogLevel(get(LoggerAdmin.class), 086 context.getBundle()); 087 } 088 ((LogManager) logMgr).setForwarder(this); 089 }); 090 setOnDissolving(() -> { 091 ((LogManager) logMgr).setForwarder(this); 092 }); 093 } 094 095 @Override 096 public void stop(BundleContext context) throws Exception { 097 java.util.logging.LogManager logMgr 098 = java.util.logging.LogManager.getLogManager(); 099 if (logMgr instanceof LogManager) { 100 ((LogManager) logMgr).setForwarder(null); 101 } 102 super.stop(context); 103 } 104 105 private void adaptLogLevel(LoggerAdmin logAdmin, Bundle bundle) { 106 if (adaptedBundles.contains(bundle)) { 107 return; 108 } 109 LoggerContext ctx = logAdmin.getLoggerContext( 110 bundle.getSymbolicName() + "|" + bundle.getVersion()); 111 @SuppressWarnings("PMD.UseConcurrentHashMap") 112 Map<String, LogLevel> logLevels = new HashMap<>(); 113 logLevels.put(Logger.ROOT_LOGGER_NAME, LogLevel.TRACE); 114 ctx.setLogLevels(logLevels); 115 adaptedBundles.add(bundle); 116 } 117 118 @Override 119 public boolean process(LogInfo logInfo) { 120 LogService logSvc = get(LogService.class); 121 if (logSvc == null) { 122 return false; 123 } 124 doProcess(logInfo, logSvc); 125 return true; 126 } 127 128 private void doProcess(LogInfo logInfo, LogService service) { 129 final LogRecord record = logInfo.getLogRecord(); 130 final String loggerName = Optional.ofNullable(record.getLoggerName()) 131 .orElse(Logger.ROOT_LOGGER_NAME); 132 Logger logger = findBundle(logInfo.getCallingClass()) 133 .map(b -> { 134 if (adaptOsgiLevel) { 135 adaptLogLevel(get(LoggerAdmin.class), b); 136 } 137 return service.getLogger(b, loggerName, Logger.class); 138 }).orElse(service.getLogger(loggerName)); 139 140 int julLevel = record.getLevel().intValue(); 141 if (julLevel >= Level.SEVERE.intValue()) { 142 if (logger.isErrorEnabled()) { 143 logger.error("{}", formatMessage(logPattern, record), 144 record.getThrown()); 145 } 146 } else if (julLevel >= Level.WARNING.intValue()) { 147 if (logger.isWarnEnabled()) { 148 logger.warn("{}", formatMessage(logPattern, record), 149 record.getThrown()); 150 } 151 } else if (julLevel >= Level.INFO.intValue()) { 152 if (logger.isInfoEnabled()) { 153 logger.info("{}", formatMessage(logPattern, record), 154 record.getThrown()); 155 } 156 } else if (julLevel >= Level.FINE.intValue()) { 157 if (logger.isDebugEnabled()) { 158 logger.debug("{}", formatMessage(logPattern, record), 159 record.getThrown()); 160 } 161 } else if (logger.isTraceEnabled()) { 162 logger.trace("{}", formatMessage(logPattern, record), 163 record.getThrown()); 164 } 165 } 166 167 private Optional<Bundle> findBundle(Class<?> callingClass) { 168 if (callingClass == null) { 169 return Optional.empty(); 170 } 171 Bundle bundle = Optional.ofNullable(bundles.get(callingClass)) 172 .map(WeakReference::get).orElse(null); 173 if (bundle != null) { 174 return Optional.of(bundle); 175 } 176 bundle = FrameworkUtil.getBundle(callingClass); 177 if (bundle != null) { 178 bundles.put(callingClass, new WeakReference<>(bundle)); // NOPMD 179 return Optional.of(bundle); 180 } 181 return Optional.empty(); 182 } 183 184 private String formatMessage(String format, LogRecord record) { 185 String message = record.getMessage(); 186 if (record.getResourceBundle() != null) { 187 try { 188 message = record.getResourceBundle().getString(message); 189 } catch (MissingResourceException e) { // NOPMD 190 // Leave message as it is 191 } 192 } 193 Object[] parameters = record.getParameters(); 194 if (parameters != null && parameters.length > 0) { 195 message = MessageFormat.format(message, record.getParameters()); 196 } 197 return MessageFormat.format(format, message, record.getMillis(), 198 record.getSequenceNumber(), record.getSourceClassName(), 199 record.getSourceMethodName(), record.getThreadID()); 200 } 201 202 /** 203 * Process events that are delivered . 204 * 205 * @param logInfos the log infos 206 */ 207 @SuppressWarnings({ "PMD.EmptyCatchBlock", "PMD.UseVarargs" }) 208 public void processBuffered(LogInfo[] logInfos) { 209 String threadName = Thread.currentThread().getName(); 210 try { 211 // Set thread name to indicate invalid context 212 try { 213 AccessController.doPrivileged(new PrivilegedAction<Void>() { 214 @Override 215 public Void run() { 216 Thread.currentThread().setName("(log flusher)"); 217 return null; 218 } 219 }); 220 } catch (SecurityException e) { 221 // Ignored, was just a best effort. 222 } 223 // Now process 224 for (LogInfo info : logInfos) { 225 process(info); 226 } 227 } finally { 228 try { 229 AccessController.doPrivileged(new PrivilegedAction<Void>() { 230 231 @Override 232 public Void run() { 233 Thread.currentThread().setName(threadName); 234 return null; // NOPMD 235 } 236 }); 237 } catch (SecurityException e) { 238 // Ignored. If resetting doesn't work, setting hasn't worked 239 // neither 240 } 241 } 242 } 243 244}