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(null); 092 adaptedBundles.clear(); 093 }); 094 } 095 096 @Override 097 public void stop(BundleContext context) throws Exception { 098 java.util.logging.LogManager logMgr 099 = java.util.logging.LogManager.getLogManager(); 100 if (logMgr instanceof LogManager) { 101 ((LogManager) logMgr).setForwarder(null); 102 } 103 super.stop(context); 104 } 105 106 private void adaptLogLevel(LoggerAdmin logAdmin, Bundle bundle) { 107 if (adaptedBundles.contains(bundle)) { 108 return; 109 } 110 LoggerContext ctx = logAdmin.getLoggerContext( 111 bundle.getSymbolicName() + "|" + bundle.getVersion()); 112 @SuppressWarnings("PMD.UseConcurrentHashMap") 113 Map<String, LogLevel> logLevels = new HashMap<>(); 114 logLevels.put(Logger.ROOT_LOGGER_NAME, LogLevel.TRACE); 115 ctx.setLogLevels(logLevels); 116 adaptedBundles.add(bundle); 117 } 118 119 @Override 120 public boolean process(LogInfo logInfo) { 121 LogService logSvc = get(LogService.class); 122 if (logSvc == null) { 123 return false; 124 } 125 doProcess(logInfo, logSvc); 126 return true; 127 } 128 129 private void doProcess(LogInfo logInfo, LogService service) { 130 final LogRecord record = logInfo.getLogRecord(); 131 final String loggerName = Optional.ofNullable(record.getLoggerName()) 132 .orElse(Logger.ROOT_LOGGER_NAME); 133 Logger logger = findBundle(logInfo.getCallingClass()) 134 .map(b -> { 135 if (adaptOsgiLevel) { 136 adaptLogLevel(get(LoggerAdmin.class), b); 137 } 138 return service.getLogger(b, loggerName, Logger.class); 139 }).orElse(service.getLogger(loggerName)); 140 141 int julLevel = record.getLevel().intValue(); 142 if (julLevel >= Level.SEVERE.intValue()) { 143 if (logger.isErrorEnabled()) { 144 logger.error("{}", formatMessage(logPattern, record), 145 record.getThrown()); 146 } 147 } else if (julLevel >= Level.WARNING.intValue()) { 148 if (logger.isWarnEnabled()) { 149 logger.warn("{}", formatMessage(logPattern, record), 150 record.getThrown()); 151 } 152 } else if (julLevel >= Level.INFO.intValue()) { 153 if (logger.isInfoEnabled()) { 154 logger.info("{}", formatMessage(logPattern, record), 155 record.getThrown()); 156 } 157 } else if (julLevel >= Level.FINE.intValue()) { 158 if (logger.isDebugEnabled()) { 159 logger.debug("{}", formatMessage(logPattern, record), 160 record.getThrown()); 161 } 162 } else if (logger.isTraceEnabled()) { 163 logger.trace("{}", formatMessage(logPattern, record), 164 record.getThrown()); 165 } 166 } 167 168 private Optional<Bundle> findBundle(Class<?> callingClass) { 169 if (callingClass == null) { 170 return Optional.empty(); 171 } 172 Bundle bundle = Optional.ofNullable(bundles.get(callingClass)) 173 .map(WeakReference::get).orElse(null); 174 if (bundle != null) { 175 return Optional.of(bundle); 176 } 177 bundle = FrameworkUtil.getBundle(callingClass); 178 if (bundle != null) { 179 bundles.put(callingClass, new WeakReference<>(bundle)); // NOPMD 180 return Optional.of(bundle); 181 } 182 return Optional.empty(); 183 } 184 185 private String formatMessage(String format, LogRecord record) { 186 String message = record.getMessage(); 187 if (record.getResourceBundle() != null) { 188 try { 189 message = record.getResourceBundle().getString(message); 190 } catch (MissingResourceException e) { // NOPMD 191 // Leave message as it is 192 } 193 } 194 Object[] parameters = record.getParameters(); 195 if (parameters != null && parameters.length > 0) { 196 message = MessageFormat.format(message, record.getParameters()); 197 } 198 return MessageFormat.format(format, message, record.getMillis(), 199 record.getSequenceNumber(), record.getSourceClassName(), 200 record.getSourceMethodName(), record.getThreadID()); 201 } 202 203 /** 204 * Process events that are delivered . 205 * 206 * @param logInfos the log infos 207 */ 208 @SuppressWarnings({ "PMD.EmptyCatchBlock", "PMD.UseVarargs", 209 "PMD.AvoidInstantiatingObjectsInLoops" }) 210 public void processBuffered(LogInfo[] logInfos) { 211 String threadName = Thread.currentThread().getName(); 212 for (LogInfo info : logInfos) { 213 try { 214 // Set thread name to indicate invalid context 215 try { 216 AccessController.doPrivileged(new PrivilegedAction<Void>() { 217 @Override 218 public Void run() { 219 Thread.currentThread() 220 .setName(info.getThreadName() + " [recorded]"); 221 return null; 222 } 223 }); 224 } catch (SecurityException e) { 225 // Ignored, was just a best effort. 226 } 227 // Now process 228 process(info); 229 } finally { 230 try { 231 AccessController.doPrivileged(new PrivilegedAction<Void>() { 232 233 @Override 234 public Void run() { 235 Thread.currentThread().setName(threadName); 236 return null; // NOPMD 237 } 238 }); 239 } catch (SecurityException e) { 240 // Ignored. If resetting doesn't work, setting hasn't worked 241 // neither 242 } 243 } 244 } 245 } 246 247}