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; 023import java.lang.ref.WeakReference; 024import java.security.AccessController; 025import java.security.PrivilegedAction; 026import java.text.MessageFormat; 027import java.util.Collections; 028import java.util.HashMap; 029import java.util.Map; 030import java.util.MissingResourceException; 031import java.util.Optional; 032import java.util.Set; 033import java.util.WeakHashMap; 034import java.util.concurrent.ConcurrentHashMap; 035import java.util.logging.Level; 036import java.util.logging.LogRecord; 037import org.osgi.framework.Bundle; 038import org.osgi.framework.BundleContext; 039import org.osgi.framework.FrameworkUtil; 040import org.osgi.service.log.LogLevel; 041import org.osgi.service.log.LogService; 042import org.osgi.service.log.Logger; 043import org.osgi.service.log.admin.LoggerAdmin; 044import org.osgi.service.log.admin.LoggerContext; 045 046/** 047 */ 048@SuppressWarnings("PMD.DataflowAnomalyAnalysis") 049public class Forwarder extends ServiceResolver implements LogRecordHandler { 050 051 private String logPattern; 052 private boolean adaptOsgiLevel = true; 053 private final Map<Class<?>, WeakReference<Bundle>> bundles 054 = new ConcurrentHashMap<>(); 055 private final Set<Bundle> adaptedBundles = Collections 056 .synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); 057 058 @Override 059 @SuppressWarnings({ "PMD.SystemPrintln" }) 060 protected void configure() { 061 final java.util.logging.LogManager logMgr 062 = java.util.logging.LogManager.getLogManager(); 063 if (!(logMgr instanceof LogManager)) { 064 System.err.println("Configuration error: " 065 + "Bundle de.mnl.osgi.jul2osgi must be used with " 066 + "LogManager from de.mnl.osgi.jul2osgi.log."); 067 return; 068 } 069 logPattern = Optional.ofNullable( 070 context.getProperty("de.mnl.osgi.jul2osgi.logPattern")) 071 .orElse("{0}"); 072 String adaptProperty = context.getProperty( 073 "de.mnl.osgi.jul2osgi.adaptOSGiLevel"); 074 if (adaptProperty != null) { 075 adaptOsgiLevel = Boolean.parseBoolean(adaptProperty); 076 } 077 078 addDependency(LogService.class); 079 addDependency(LoggerAdmin.class); 080 setOnResolved(() -> { 081 if (adaptOsgiLevel) { 082 // Handle this bundle specially 083 adaptLogLevel(get(LoggerAdmin.class), 084 context.getBundle()); 085 } 086 ((LogManager) logMgr).setForwarder(this); 087 }); 088 setOnDissolving(() -> { 089 ((LogManager) logMgr).setForwarder(null); 090 adaptedBundles.clear(); 091 }); 092 } 093 094 @Override 095 public void stop(BundleContext context) throws Exception { 096 java.util.logging.LogManager logMgr 097 = java.util.logging.LogManager.getLogManager(); 098 if (logMgr instanceof LogManager) { 099 ((LogManager) logMgr).setForwarder(null); 100 } 101 super.stop(context); 102 } 103 104 private void adaptLogLevel(LoggerAdmin logAdmin, Bundle bundle) { 105 if (adaptedBundles.contains(bundle)) { 106 return; 107 } 108 LoggerContext ctx = logAdmin.getLoggerContext( 109 bundle.getSymbolicName() + "|" + bundle.getVersion()); 110 @SuppressWarnings("PMD.UseConcurrentHashMap") 111 Map<String, LogLevel> logLevels = new HashMap<>(); 112 logLevels.put(Logger.ROOT_LOGGER_NAME, LogLevel.TRACE); 113 ctx.setLogLevels(logLevels); 114 adaptedBundles.add(bundle); 115 } 116 117 @Override 118 public boolean process(LogInfo logInfo) { 119 LogService logSvc = get(LogService.class); 120 if (logSvc == null) { 121 return false; 122 } 123 doProcess(logInfo, logSvc); 124 return true; 125 } 126 127 @SuppressWarnings("PMD.CognitiveComplexity") 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 "PMD.AvoidInstantiatingObjectsInLoops" }) 209 public void processBuffered(LogInfo[] logInfos) { 210 String threadName = Thread.currentThread().getName(); 211 for (LogInfo info : logInfos) { 212 try { 213 // Set thread name to indicate invalid context 214 try { 215 AccessController.doPrivileged(new PrivilegedAction<Void>() { 216 @Override 217 public Void run() { 218 Thread.currentThread() 219 .setName(info.getThreadName() + " [recorded]"); 220 return null; 221 } 222 }); 223 } catch (SecurityException e) { 224 // Ignored, was just a best effort. 225 } 226 // Now process 227 process(info); 228 } finally { 229 try { 230 AccessController.doPrivileged(new PrivilegedAction<Void>() { 231 232 @Override 233 public Void run() { 234 Thread.currentThread().setName(threadName); 235 return null; // NOPMD 236 } 237 }); 238 } catch (SecurityException e) { 239 // Ignored. If resetting doesn't work, setting hasn't worked 240 // neither 241 } 242 } 243 } 244 } 245 246}