1 package org
.argeo
.cms
.internal
.osgi
;
3 import java
.io
.IOException
;
4 import java
.nio
.file
.FileSystems
;
5 import java
.nio
.file
.Path
;
6 import java
.nio
.file
.StandardWatchEventKinds
;
7 import java
.nio
.file
.WatchEvent
;
8 import java
.nio
.file
.WatchKey
;
9 import java
.nio
.file
.WatchService
;
10 import java
.security
.SignatureException
;
11 import java
.util
.ArrayList
;
12 import java
.util
.Collections
;
13 import java
.util
.Enumeration
;
14 import java
.util
.HashMap
;
15 import java
.util
.Iterator
;
16 import java
.util
.LinkedList
;
17 import java
.util
.List
;
18 import java
.util
.ListIterator
;
20 import java
.util
.Properties
;
21 import java
.util
.concurrent
.BlockingQueue
;
22 import java
.util
.concurrent
.LinkedBlockingQueue
;
24 import org
.argeo
.api
.cms
.CmsConstants
;
25 import org
.argeo
.api
.cms
.CmsLog
;
26 import org
.argeo
.cms
.ArgeoLogListener
;
27 import org
.argeo
.cms
.ArgeoLogger
;
28 import org
.argeo
.cms
.CmsException
;
29 import org
.argeo
.cms
.auth
.CurrentUser
;
30 import org
.argeo
.cms
.internal
.runtime
.KernelConstants
;
31 import org
.argeo
.osgi
.useradmin
.UserAdminConf
;
32 import org
.osgi
.framework
.Bundle
;
33 import org
.osgi
.framework
.Constants
;
34 import org
.osgi
.framework
.ServiceReference
;
35 import org
.osgi
.service
.cm
.ConfigurationAdmin
;
36 import org
.osgi
.service
.log
.LogEntry
;
37 import org
.osgi
.service
.log
.LogLevel
;
38 import org
.osgi
.service
.log
.LogListener
;
39 import org
.osgi
.service
.log
.LogReaderService
;
41 /** Not meant to be used directly in standard log4j config */
42 public class CmsOsgiLogger
implements ArgeoLogger
, LogListener
{
43 /** Internal debug for development purposes. */
44 private static Boolean debug
= false;
46 private Boolean disabled
= false;
48 private String level
= null;
50 // private Level log4jLevel = null;
52 private Properties configuration
;
54 private AppenderImpl appender
;
56 private final List
<ArgeoLogListener
> everythingListeners
= Collections
57 .synchronizedList(new ArrayList
<ArgeoLogListener
>());
58 private final List
<ArgeoLogListener
> allUsersListeners
= Collections
59 .synchronizedList(new ArrayList
<ArgeoLogListener
>());
60 private final Map
<String
, List
<ArgeoLogListener
>> userListeners
= Collections
61 .synchronizedMap(new HashMap
<String
, List
<ArgeoLogListener
>>());
63 private BlockingQueue
<LogEvent
> events
;
64 private LogDispatcherThread logDispatcherThread
= new LogDispatcherThread();
66 private Integer maxLastEventsCount
= 10 * 1000;
68 /** Marker to prevent stack overflow */
69 private ThreadLocal
<Boolean
> dispatching
= new ThreadLocal
<Boolean
>() {
72 protected Boolean
initialValue() {
77 public CmsOsgiLogger(LogReaderService lrs
) {
79 Enumeration
<LogEntry
> logEntries
= lrs
.getLog();
80 while (logEntries
.hasMoreElements())
81 logged(logEntries
.nextElement());
82 lrs
.addLogListener(this);
84 // configure log4j watcher
85 // String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration");
86 // if (log4jConfiguration != null && log4jConfiguration.startsWith("file:")) {
87 // if (log4jConfiguration.contains("..")) {
88 // if (log4jConfiguration.startsWith("file://"))
89 // log4jConfiguration = log4jConfiguration.substring("file://".length());
90 // else if (log4jConfiguration.startsWith("file:"))
91 // log4jConfiguration = log4jConfiguration.substring("file:".length());
94 // Path log4jconfigPath;
95 // if (log4jConfiguration.startsWith("file:"))
96 // log4jconfigPath = Paths.get(new URI(log4jConfiguration));
98 // log4jconfigPath = Paths.get(log4jConfiguration);
99 // Thread log4jConfWatcher = new Log4jConfWatcherThread(log4jconfigPath);
100 // log4jConfWatcher.start();
101 // } catch (Exception e) {
102 // stdErr("Badly formatted log4j configuration URI " + log4jConfiguration + ": " + e.getMessage());
110 events
= new LinkedBlockingQueue
<LogEvent
>();
112 // if (layout != null)
113 // setLayout(layout);
115 // setLayout(new PatternLayout(pattern));
116 appender
= new AppenderImpl();
117 reloadConfiguration();
118 // Logger.getRootLogger().addAppender(appender);
120 logDispatcherThread
= new LogDispatcherThread();
121 logDispatcherThread
.start();
122 } catch (Exception e
) {
123 throw new CmsException("Cannot initialize log4j");
127 public void destroy() throws Exception
{
128 // Logger.getRootLogger().removeAppender(appender);
129 allUsersListeners
.clear();
130 for (List
<ArgeoLogListener
> lst
: userListeners
.values())
132 userListeners
.clear();
136 logDispatcherThread
.interrupt();
139 // public void setLayout(Layout layout) {
140 // this.layout = layout;
143 public String
toString() {
144 return "Node Logger";
151 public void logged(LogEntry status
) {
152 CmsLog pluginLog
= CmsLog
.getLog(status
.getBundle().getSymbolicName());
153 LogLevel severity
= status
.getLogLevel();
154 if (severity
.equals(LogLevel
.ERROR
) && pluginLog
.isErrorEnabled()) {
155 // FIXME Fix Argeo TP
156 if (status
.getException() instanceof SignatureException
)
158 pluginLog
.error(msg(status
), status
.getException());
159 } else if (severity
.equals(LogLevel
.WARN
) && pluginLog
.isWarnEnabled()) {
160 if ("org.apache.felix.scr".equals(status
.getBundle().getSymbolicName())
161 && (status
.getException() != null && status
.getException() instanceof InterruptedException
)) {
162 // do not print stacktraces by Felix SCR shutdown
163 pluginLog
.warn(msg(status
));
165 pluginLog
.warn(msg(status
), status
.getException());
167 } else if (severity
.equals(LogLevel
.INFO
) && pluginLog
.isDebugEnabled())
168 pluginLog
.debug(msg(status
), status
.getException());
169 else if (severity
.equals(LogLevel
.DEBUG
) && pluginLog
.isTraceEnabled())
170 pluginLog
.trace(msg(status
), status
.getException());
171 else if (severity
.equals(LogLevel
.TRACE
) && pluginLog
.isTraceEnabled())
172 pluginLog
.trace(msg(status
), status
.getException());
175 private String
msg(LogEntry status
) {
176 StringBuilder sb
= new StringBuilder();
177 sb
.append(status
.getMessage());
178 Bundle bundle
= status
.getBundle();
179 if (bundle
!= null) {
180 sb
.append(" '" + bundle
.getSymbolicName() + "'");
182 ServiceReference
<?
> sr
= status
.getServiceReference();
185 String
[] objectClasses
= (String
[]) sr
.getProperty(Constants
.OBJECTCLASS
);
186 if (isSpringApplicationContext(objectClasses
)) {
187 sb
.append("{org.springframework.context.ApplicationContext}");
188 Object symbolicName
= sr
.getProperty(Constants
.BUNDLE_SYMBOLICNAME
);
189 if (symbolicName
!= null)
190 sb
.append(" " + Constants
.BUNDLE_SYMBOLICNAME
+ ": " + symbolicName
);
192 sb
.append(arrayToString(objectClasses
));
194 Object cn
= sr
.getProperty(CmsConstants
.CN
);
196 sb
.append(" " + CmsConstants
.CN
+ ": " + cn
);
197 Object factoryPid
= sr
.getProperty(ConfigurationAdmin
.SERVICE_FACTORYPID
);
198 if (factoryPid
!= null)
199 sb
.append(" " + ConfigurationAdmin
.SERVICE_FACTORYPID
+ ": " + factoryPid
);
201 // Object servicePid = sr.getProperty(Constants.SERVICE_PID);
202 // if (servicePid != null)
203 // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
206 Object whiteBoardPattern
= sr
.getProperty(KernelConstants
.WHITEBOARD_PATTERN_PROP
);
207 if (whiteBoardPattern
!= null) {
208 if (whiteBoardPattern
instanceof String
) {
209 sb
.append(" " + KernelConstants
.WHITEBOARD_PATTERN_PROP
+ ": " + whiteBoardPattern
);
211 sb
.append(" " + KernelConstants
.WHITEBOARD_PATTERN_PROP
+ ": "
212 + arrayToString((String
[]) whiteBoardPattern
));
216 Object contextName
= sr
.getProperty(KernelConstants
.CONTEXT_NAME_PROP
);
217 if (contextName
!= null)
218 sb
.append(" " + KernelConstants
.CONTEXT_NAME_PROP
+ ": " + contextName
);
221 Object baseDn
= sr
.getProperty(UserAdminConf
.baseDn
.name());
223 sb
.append(" " + UserAdminConf
.baseDn
.name() + ": " + baseDn
);
226 return sb
.toString();
229 private String
arrayToString(Object
[] arr
) {
230 StringBuilder sb
= new StringBuilder();
232 for (int i
= 0; i
< arr
.length
; i
++) {
238 return sb
.toString();
241 private boolean isSpringApplicationContext(String
[] objectClasses
) {
242 for (String clss
: objectClasses
) {
243 if (clss
.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) {
254 public synchronized void register(ArgeoLogListener listener
, Integer numberOfPreviousEvents
) {
255 String username
= CurrentUser
.getUsername();
256 if (username
== null)
257 throw new CmsException("Only authenticated users can register a log listener");
259 if (!userListeners
.containsKey(username
)) {
260 List
<ArgeoLogListener
> lst
= Collections
.synchronizedList(new ArrayList
<ArgeoLogListener
>());
261 userListeners
.put(username
, lst
);
263 userListeners
.get(username
).add(listener
);
264 List
<LogEvent
> lastEvents
= logDispatcherThread
.getLastEvents(username
, numberOfPreviousEvents
);
265 for (LogEvent evt
: lastEvents
)
266 dispatchEvent(listener
, evt
);
269 public synchronized void registerForAll(ArgeoLogListener listener
, Integer numberOfPreviousEvents
,
270 boolean everything
) {
272 everythingListeners
.add(listener
);
274 allUsersListeners
.add(listener
);
275 List
<LogEvent
> lastEvents
= logDispatcherThread
.getLastEvents(null, numberOfPreviousEvents
);
276 for (LogEvent evt
: lastEvents
)
277 if (everything
|| evt
.getUsername() != null)
278 dispatchEvent(listener
, evt
);
281 public synchronized void unregister(ArgeoLogListener listener
) {
282 String username
= CurrentUser
.getUsername();
283 if (username
== null)// FIXME
285 if (!userListeners
.containsKey(username
))
286 throw new IllegalStateException("No user listeners " + listener
+ " registered for user " + username
);
287 if (!userListeners
.get(username
).contains(listener
))
288 throw new IllegalStateException("No user listeners " + listener
+ " registered for user " + username
);
289 userListeners
.get(username
).remove(listener
);
290 if (userListeners
.get(username
).isEmpty())
291 userListeners
.remove(username
);
295 public synchronized void unregisterForAll(ArgeoLogListener listener
) {
296 everythingListeners
.remove(listener
);
297 allUsersListeners
.remove(listener
);
300 /** For development purpose, since using regular logging is not easy here */
301 private static void stdOut(Object obj
) {
302 System
.out
.println(obj
);
305 private static void stdErr(Object obj
) {
306 System
.err
.println(obj
);
309 private static void debug(Object obj
) {
311 System
.out
.println(obj
);
314 private static boolean isInternalDebugEnabled() {
318 // public void setPattern(String pattern) {
319 // this.pattern = pattern;
322 public void setDisabled(Boolean disabled
) {
323 this.disabled
= disabled
;
326 public void setLevel(String level
) {
330 public void setConfiguration(Properties configuration
) {
331 this.configuration
= configuration
;
334 public void updateConfiguration(Properties configuration
) {
335 setConfiguration(configuration
);
336 reloadConfiguration();
339 public Properties
getConfiguration() {
340 return configuration
;
344 * Reloads configuration (if the configuration {@link Properties} is set)
346 protected void reloadConfiguration() {
347 if (configuration
!= null) {
348 // LogManager.resetConfiguration();
349 // PropertyConfigurator.configure(configuration);
353 protected synchronized void processLoggingEvent(LogEvent event
) {
357 if (dispatching
.get())
360 if (level
!= null && !level
.trim().equals("")) {
361 // if (log4jLevel == null || !log4jLevel.toString().equals(level))
363 // log4jLevel = Level.toLevel(level);
364 // } catch (Exception e) {
365 // System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
366 // e.printStackTrace();
370 // if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
377 Iterator
<ArgeoLogListener
> everythingIt
= everythingListeners
.iterator();
378 while (everythingIt
.hasNext())
379 dispatchEvent(everythingIt
.next(), event
);
381 if (event
.getUsername() != null) {
382 Iterator
<ArgeoLogListener
> allUsersIt
= allUsersListeners
.iterator();
383 while (allUsersIt
.hasNext())
384 dispatchEvent(allUsersIt
.next(), event
);
386 if (userListeners
.containsKey(event
.getUsername())) {
387 Iterator
<ArgeoLogListener
> userIt
= userListeners
.get(event
.getUsername()).iterator();
388 while (userIt
.hasNext())
389 dispatchEvent(userIt
.next(), event
);
392 } catch (Exception e
) {
393 stdOut("Cannot process logging event");
398 protected void dispatchEvent(ArgeoLogListener logListener
, LogEvent evt
) {
399 // LoggingEvent event = evt.getLoggingEvent();
400 // logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
401 // event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
404 private class AppenderImpl
{ // extends AppenderSkeleton {
405 public boolean requiresLayout() {
409 public void close() {
413 // protected void append(LoggingEvent event) {
414 // if (events != null) {
416 // String username = CurrentUser.getUsername();
417 // events.put(new LogEvent(username, event));
418 // } catch (InterruptedException e) {
426 private class LogDispatcherThread
extends Thread
{
427 /** encapsulated in order to simplify concurrency management */
428 private LinkedList
<LogEvent
> lastEvents
= new LinkedList
<LogEvent
>();
430 public LogDispatcherThread() {
431 super("Argeo Logging Dispatcher Thread");
435 while (events
!= null) {
437 LogEvent loggingEvent
= events
.take();
438 processLoggingEvent(loggingEvent
);
439 addLastEvent(loggingEvent
);
440 } catch (InterruptedException e
) {
447 protected synchronized void addLastEvent(LogEvent loggingEvent
) {
448 if (lastEvents
.size() >= maxLastEventsCount
)
450 lastEvents
.add(loggingEvent
);
453 public synchronized List
<LogEvent
> getLastEvents(String username
, Integer maxCount
) {
454 LinkedList
<LogEvent
> evts
= new LinkedList
<LogEvent
>();
455 ListIterator
<LogEvent
> it
= lastEvents
.listIterator(lastEvents
.size());
457 while (it
.hasPrevious() && (count
< maxCount
)) {
458 LogEvent evt
= it
.previous();
459 if (username
== null || username
.equals(evt
.getUsername())) {
468 private class LogEvent
{
469 private final String username
;
470 // private final LoggingEvent loggingEvent;
472 public LogEvent(String username
) {
474 this.username
= username
;
475 // this.loggingEvent = loggingEvent;
479 // public int hashCode() {
480 // return loggingEvent.hashCode();
484 // public boolean equals(Object obj) {
485 // return loggingEvent.equals(obj);
489 // public String toString() {
490 // return username + "@ " + loggingEvent.toString();
493 public String
getUsername() {
497 // public LoggingEvent getLoggingEvent() {
498 // return loggingEvent;
503 private class Log4jConfWatcherThread
extends Thread
{
504 private Path log4jConfigurationPath
;
506 public Log4jConfWatcherThread(Path log4jConfigurationPath
) {
507 super("Log4j Configuration Watcher");
509 this.log4jConfigurationPath
= log4jConfigurationPath
.toRealPath();
510 } catch (IOException e
) {
511 this.log4jConfigurationPath
= log4jConfigurationPath
.toAbsolutePath();
512 stdOut("Cannot determine real path for " + log4jConfigurationPath
+ ": " + e
.getMessage());
517 Path parentDir
= log4jConfigurationPath
.getParent();
518 try (final WatchService watchService
= FileSystems
.getDefault().newWatchService()) {
519 parentDir
.register(watchService
, StandardWatchEventKinds
.ENTRY_MODIFY
);
521 watching
: while ((wk
= watchService
.take()) != null) {
522 for (WatchEvent
<?
> event
: wk
.pollEvents()) {
523 final Path changed
= (Path
) event
.context();
524 if (log4jConfigurationPath
.equals(parentDir
.resolve(changed
))) {
525 if (isInternalDebugEnabled())
526 debug(log4jConfigurationPath
+ " has changed, reloading.");
527 // PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
531 boolean valid
= wk
.reset();
536 } catch (IOException
| InterruptedException e
) {
537 stdErr("Log4j configuration watcher failed: " + e
.getMessage());