]> git.argeo.org Git - lgpl/argeo-commons.git/blob - CmsOsgiLogger.java
6898c4348fe098dc9113cfb25e62d1c39c98c6d2
[lgpl/argeo-commons.git] / CmsOsgiLogger.java
1 package org.argeo.cms.internal.osgi;
2
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;
19 import java.util.Map;
20 import java.util.Properties;
21 import java.util.concurrent.BlockingQueue;
22 import java.util.concurrent.LinkedBlockingQueue;
23
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;
40
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;
45
46 private Boolean disabled = false;
47
48 private String level = null;
49
50 // private Level log4jLevel = null;
51
52 private Properties configuration;
53
54 private AppenderImpl appender;
55
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>>());
62
63 private BlockingQueue<LogEvent> events;
64 private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
65
66 private Integer maxLastEventsCount = 10 * 1000;
67
68 /** Marker to prevent stack overflow */
69 private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
70
71 @Override
72 protected Boolean initialValue() {
73 return false;
74 }
75 };
76
77 public CmsOsgiLogger(LogReaderService lrs) {
78 if (lrs != null) {
79 Enumeration<LogEntry> logEntries = lrs.getLog();
80 while (logEntries.hasMoreElements())
81 logged(logEntries.nextElement());
82 lrs.addLogListener(this);
83
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());
92 // }
93 // try {
94 // Path log4jconfigPath;
95 // if (log4jConfiguration.startsWith("file:"))
96 // log4jconfigPath = Paths.get(new URI(log4jConfiguration));
97 // else
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());
103 // }
104 // }
105 }
106 }
107
108 public void init() {
109 try {
110 events = new LinkedBlockingQueue<LogEvent>();
111
112 // if (layout != null)
113 // setLayout(layout);
114 // else
115 // setLayout(new PatternLayout(pattern));
116 appender = new AppenderImpl();
117 reloadConfiguration();
118 // Logger.getRootLogger().addAppender(appender);
119
120 logDispatcherThread = new LogDispatcherThread();
121 logDispatcherThread.start();
122 } catch (Exception e) {
123 throw new CmsException("Cannot initialize log4j");
124 }
125 }
126
127 public void destroy() throws Exception {
128 // Logger.getRootLogger().removeAppender(appender);
129 allUsersListeners.clear();
130 for (List<ArgeoLogListener> lst : userListeners.values())
131 lst.clear();
132 userListeners.clear();
133
134 events.clear();
135 events = null;
136 logDispatcherThread.interrupt();
137 }
138
139 // public void setLayout(Layout layout) {
140 // this.layout = layout;
141 // }
142
143 public String toString() {
144 return "Node Logger";
145 }
146
147 //
148 // OSGi LOGGER
149 //
150 @Override
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)
157 return;
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));
164 } else {
165 pluginLog.warn(msg(status), status.getException());
166 }
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());
173 }
174
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() + "'");
181 }
182 ServiceReference<?> sr = status.getServiceReference();
183 if (sr != null) {
184 sb.append(' ');
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);
191 } else {
192 sb.append(arrayToString(objectClasses));
193 }
194 Object cn = sr.getProperty(CmsConstants.CN);
195 if (cn != null)
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);
200 // else {
201 // Object servicePid = sr.getProperty(Constants.SERVICE_PID);
202 // if (servicePid != null)
203 // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
204 // }
205 // servlets
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);
210 } else {
211 sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
212 + arrayToString((String[]) whiteBoardPattern));
213 }
214 }
215 // RWT
216 Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP);
217 if (contextName != null)
218 sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
219
220 // user directories
221 Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
222 if (baseDn != null)
223 sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
224
225 }
226 return sb.toString();
227 }
228
229 private String arrayToString(Object[] arr) {
230 StringBuilder sb = new StringBuilder();
231 sb.append('[');
232 for (int i = 0; i < arr.length; i++) {
233 if (i != 0)
234 sb.append(',');
235 sb.append(arr[i]);
236 }
237 sb.append(']');
238 return sb.toString();
239 }
240
241 private boolean isSpringApplicationContext(String[] objectClasses) {
242 for (String clss : objectClasses) {
243 if (clss.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) {
244 return true;
245 }
246 }
247 return false;
248 }
249
250 //
251 // ARGEO LOGGER
252 //
253
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");
258
259 if (!userListeners.containsKey(username)) {
260 List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
261 userListeners.put(username, lst);
262 }
263 userListeners.get(username).add(listener);
264 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
265 for (LogEvent evt : lastEvents)
266 dispatchEvent(listener, evt);
267 }
268
269 public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
270 boolean everything) {
271 if (everything)
272 everythingListeners.add(listener);
273 else
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);
279 }
280
281 public synchronized void unregister(ArgeoLogListener listener) {
282 String username = CurrentUser.getUsername();
283 if (username == null)// FIXME
284 return;
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);
292
293 }
294
295 public synchronized void unregisterForAll(ArgeoLogListener listener) {
296 everythingListeners.remove(listener);
297 allUsersListeners.remove(listener);
298 }
299
300 /** For development purpose, since using regular logging is not easy here */
301 private static void stdOut(Object obj) {
302 System.out.println(obj);
303 }
304
305 private static void stdErr(Object obj) {
306 System.err.println(obj);
307 }
308
309 private static void debug(Object obj) {
310 if (debug)
311 System.out.println(obj);
312 }
313
314 private static boolean isInternalDebugEnabled() {
315 return debug;
316 }
317
318 // public void setPattern(String pattern) {
319 // this.pattern = pattern;
320 // }
321
322 public void setDisabled(Boolean disabled) {
323 this.disabled = disabled;
324 }
325
326 public void setLevel(String level) {
327 this.level = level;
328 }
329
330 public void setConfiguration(Properties configuration) {
331 this.configuration = configuration;
332 }
333
334 public void updateConfiguration(Properties configuration) {
335 setConfiguration(configuration);
336 reloadConfiguration();
337 }
338
339 public Properties getConfiguration() {
340 return configuration;
341 }
342
343 /**
344 * Reloads configuration (if the configuration {@link Properties} is set)
345 */
346 protected void reloadConfiguration() {
347 if (configuration != null) {
348 // LogManager.resetConfiguration();
349 // PropertyConfigurator.configure(configuration);
350 }
351 }
352
353 protected synchronized void processLoggingEvent(LogEvent event) {
354 if (disabled)
355 return;
356
357 if (dispatching.get())
358 return;
359
360 if (level != null && !level.trim().equals("")) {
361 // if (log4jLevel == null || !log4jLevel.toString().equals(level))
362 // try {
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();
367 // level = null;
368 // }
369 //
370 // if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
371 // return;
372 // }
373 }
374
375 try {
376 // admin listeners
377 Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
378 while (everythingIt.hasNext())
379 dispatchEvent(everythingIt.next(), event);
380
381 if (event.getUsername() != null) {
382 Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
383 while (allUsersIt.hasNext())
384 dispatchEvent(allUsersIt.next(), event);
385
386 if (userListeners.containsKey(event.getUsername())) {
387 Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
388 while (userIt.hasNext())
389 dispatchEvent(userIt.next(), event);
390 }
391 }
392 } catch (Exception e) {
393 stdOut("Cannot process logging event");
394 e.printStackTrace();
395 }
396 }
397
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());
402 }
403
404 private class AppenderImpl { // extends AppenderSkeleton {
405 public boolean requiresLayout() {
406 return false;
407 }
408
409 public void close() {
410 }
411
412 // @Override
413 // protected void append(LoggingEvent event) {
414 // if (events != null) {
415 // try {
416 // String username = CurrentUser.getUsername();
417 // events.put(new LogEvent(username, event));
418 // } catch (InterruptedException e) {
419 // // silent
420 // }
421 // }
422 // }
423
424 }
425
426 private class LogDispatcherThread extends Thread {
427 /** encapsulated in order to simplify concurrency management */
428 private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
429
430 public LogDispatcherThread() {
431 super("Argeo Logging Dispatcher Thread");
432 }
433
434 public void run() {
435 while (events != null) {
436 try {
437 LogEvent loggingEvent = events.take();
438 processLoggingEvent(loggingEvent);
439 addLastEvent(loggingEvent);
440 } catch (InterruptedException e) {
441 if (events == null)
442 return;
443 }
444 }
445 }
446
447 protected synchronized void addLastEvent(LogEvent loggingEvent) {
448 if (lastEvents.size() >= maxLastEventsCount)
449 lastEvents.poll();
450 lastEvents.add(loggingEvent);
451 }
452
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());
456 int count = 0;
457 while (it.hasPrevious() && (count < maxCount)) {
458 LogEvent evt = it.previous();
459 if (username == null || username.equals(evt.getUsername())) {
460 evts.push(evt);
461 count++;
462 }
463 }
464 return evts;
465 }
466 }
467
468 private class LogEvent {
469 private final String username;
470 // private final LoggingEvent loggingEvent;
471
472 public LogEvent(String username) {
473 super();
474 this.username = username;
475 // this.loggingEvent = loggingEvent;
476 }
477
478 // @Override
479 // public int hashCode() {
480 // return loggingEvent.hashCode();
481 // }
482 //
483 // @Override
484 // public boolean equals(Object obj) {
485 // return loggingEvent.equals(obj);
486 // }
487 //
488 // @Override
489 // public String toString() {
490 // return username + "@ " + loggingEvent.toString();
491 // }
492
493 public String getUsername() {
494 return username;
495 }
496
497 // public LoggingEvent getLoggingEvent() {
498 // return loggingEvent;
499 // }
500
501 }
502
503 private class Log4jConfWatcherThread extends Thread {
504 private Path log4jConfigurationPath;
505
506 public Log4jConfWatcherThread(Path log4jConfigurationPath) {
507 super("Log4j Configuration Watcher");
508 try {
509 this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
510 } catch (IOException e) {
511 this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
512 stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
513 }
514 }
515
516 public void run() {
517 Path parentDir = log4jConfigurationPath.getParent();
518 try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
519 parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
520 WatchKey wk;
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());
528 }
529 }
530 // reset the key
531 boolean valid = wk.reset();
532 if (!valid) {
533 break watching;
534 }
535 }
536 } catch (IOException | InterruptedException e) {
537 stdErr("Log4j configuration watcher failed: " + e.getMessage());
538 }
539 }
540 }
541 }