1 package org
.argeo
.cms
.internal
.osgi
;
3 import java
.io
.IOException
;
5 import java
.nio
.file
.FileSystems
;
6 import java
.nio
.file
.Path
;
7 import java
.nio
.file
.Paths
;
8 import java
.nio
.file
.StandardWatchEventKinds
;
9 import java
.nio
.file
.WatchEvent
;
10 import java
.nio
.file
.WatchKey
;
11 import java
.nio
.file
.WatchService
;
12 import java
.security
.SignatureException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Collections
;
15 import java
.util
.Enumeration
;
16 import java
.util
.HashMap
;
17 import java
.util
.Iterator
;
18 import java
.util
.LinkedList
;
19 import java
.util
.List
;
20 import java
.util
.ListIterator
;
22 import java
.util
.Properties
;
23 import java
.util
.concurrent
.BlockingQueue
;
24 import java
.util
.concurrent
.LinkedBlockingQueue
;
26 import org
.argeo
.api
.cms
.CmsConstants
;
27 import org
.argeo
.api
.cms
.CmsLog
;
28 import org
.argeo
.cms
.ArgeoLogListener
;
29 import org
.argeo
.cms
.ArgeoLogger
;
30 import org
.argeo
.cms
.CmsException
;
31 import org
.argeo
.cms
.auth
.CurrentUser
;
32 import org
.argeo
.cms
.internal
.runtime
.KernelConstants
;
33 import org
.argeo
.cms
.internal
.runtime
.KernelUtils
;
34 import org
.argeo
.osgi
.useradmin
.UserAdminConf
;
35 import org
.osgi
.framework
.Bundle
;
36 import org
.osgi
.framework
.Constants
;
37 import org
.osgi
.framework
.ServiceReference
;
38 import org
.osgi
.service
.cm
.ConfigurationAdmin
;
39 import org
.osgi
.service
.log
.LogEntry
;
40 import org
.osgi
.service
.log
.LogLevel
;
41 import org
.osgi
.service
.log
.LogListener
;
42 import org
.osgi
.service
.log
.LogReaderService
;
44 /** Not meant to be used directly in standard log4j config */
45 public class NodeLogger
implements ArgeoLogger
, LogListener
{
46 /** Internal debug for development purposes. */
47 private static Boolean debug
= false;
49 private Boolean disabled
= false;
51 private String level
= null;
53 // private Level log4jLevel = null;
55 private Properties configuration
;
57 private AppenderImpl appender
;
59 private final List
<ArgeoLogListener
> everythingListeners
= Collections
60 .synchronizedList(new ArrayList
<ArgeoLogListener
>());
61 private final List
<ArgeoLogListener
> allUsersListeners
= Collections
62 .synchronizedList(new ArrayList
<ArgeoLogListener
>());
63 private final Map
<String
, List
<ArgeoLogListener
>> userListeners
= Collections
64 .synchronizedMap(new HashMap
<String
, List
<ArgeoLogListener
>>());
66 private BlockingQueue
<LogEvent
> events
;
67 private LogDispatcherThread logDispatcherThread
= new LogDispatcherThread();
69 private Integer maxLastEventsCount
= 10 * 1000;
71 /** Marker to prevent stack overflow */
72 private ThreadLocal
<Boolean
> dispatching
= new ThreadLocal
<Boolean
>() {
75 protected Boolean
initialValue() {
80 public NodeLogger(LogReaderService lrs
) {
82 Enumeration
<LogEntry
> logEntries
= lrs
.getLog();
83 while (logEntries
.hasMoreElements())
84 logged(logEntries
.nextElement());
85 lrs
.addLogListener(this);
87 // configure log4j watcher
88 String log4jConfiguration
= KernelUtils
.getFrameworkProp("log4j.configuration");
89 if (log4jConfiguration
!= null && log4jConfiguration
.startsWith("file:")) {
90 if (log4jConfiguration
.contains("..")) {
91 if (log4jConfiguration
.startsWith("file://"))
92 log4jConfiguration
= log4jConfiguration
.substring("file://".length());
93 else if (log4jConfiguration
.startsWith("file:"))
94 log4jConfiguration
= log4jConfiguration
.substring("file:".length());
98 if (log4jConfiguration
.startsWith("file:"))
99 log4jconfigPath
= Paths
.get(new URI(log4jConfiguration
));
101 log4jconfigPath
= Paths
.get(log4jConfiguration
);
102 Thread log4jConfWatcher
= new Log4jConfWatcherThread(log4jconfigPath
);
103 log4jConfWatcher
.start();
104 } catch (Exception e
) {
105 stdErr("Badly formatted log4j configuration URI " + log4jConfiguration
+ ": " + e
.getMessage());
113 events
= new LinkedBlockingQueue
<LogEvent
>();
115 // if (layout != null)
116 // setLayout(layout);
118 // setLayout(new PatternLayout(pattern));
119 appender
= new AppenderImpl();
120 reloadConfiguration();
121 // Logger.getRootLogger().addAppender(appender);
123 logDispatcherThread
= new LogDispatcherThread();
124 logDispatcherThread
.start();
125 } catch (Exception e
) {
126 throw new CmsException("Cannot initialize log4j");
130 public void destroy() throws Exception
{
131 // Logger.getRootLogger().removeAppender(appender);
132 allUsersListeners
.clear();
133 for (List
<ArgeoLogListener
> lst
: userListeners
.values())
135 userListeners
.clear();
139 logDispatcherThread
.interrupt();
142 // public void setLayout(Layout layout) {
143 // this.layout = layout;
146 public String
toString() {
147 return "Node Logger";
154 public void logged(LogEntry status
) {
155 CmsLog pluginLog
= CmsLog
.getLog(status
.getBundle().getSymbolicName());
156 LogLevel severity
= status
.getLogLevel();
157 if (severity
.equals(LogLevel
.ERROR
) && pluginLog
.isErrorEnabled()) {
158 // FIXME Fix Argeo TP
159 if (status
.getException() instanceof SignatureException
)
161 pluginLog
.error(msg(status
), status
.getException());
162 } else if (severity
.equals(LogLevel
.WARN
) && pluginLog
.isWarnEnabled()) {
163 if (pluginLog
.isTraceEnabled())
164 pluginLog
.warn(msg(status
), status
.getException());
166 pluginLog
.warn(msg(status
));
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 CmsException("No user listeners " + listener
+ " registered for user " + username
);
287 if (!userListeners
.get(username
).contains(listener
))
288 throw new CmsException("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());