]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java
Logout keyring unlock session
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / NodeLogger.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.argeo.cms.internal.kernel;
17
18 import java.io.IOException;
19 import java.net.URI;
20 import java.nio.file.FileSystems;
21 import java.nio.file.Path;
22 import java.nio.file.Paths;
23 import java.nio.file.StandardWatchEventKinds;
24 import java.nio.file.WatchEvent;
25 import java.nio.file.WatchKey;
26 import java.nio.file.WatchService;
27 import java.security.SignatureException;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.ListIterator;
36 import java.util.Map;
37 import java.util.Properties;
38 import java.util.concurrent.BlockingQueue;
39 import java.util.concurrent.LinkedBlockingQueue;
40
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43 import org.apache.log4j.AppenderSkeleton;
44 import org.apache.log4j.Level;
45 import org.apache.log4j.LogManager;
46 import org.apache.log4j.Logger;
47 import org.apache.log4j.PropertyConfigurator;
48 import org.apache.log4j.spi.LoggingEvent;
49 import org.argeo.cms.CmsException;
50 import org.argeo.cms.auth.CurrentUser;
51 import org.argeo.node.ArgeoLogListener;
52 import org.argeo.node.ArgeoLogger;
53 import org.argeo.node.NodeConstants;
54 import org.argeo.osgi.useradmin.UserAdminConf;
55 import org.osgi.framework.Constants;
56 import org.osgi.framework.ServiceReference;
57 import org.osgi.service.cm.ConfigurationAdmin;
58 import org.osgi.service.log.LogEntry;
59 import org.osgi.service.log.LogListener;
60 import org.osgi.service.log.LogReaderService;
61 import org.osgi.service.log.LogService;
62
63 /** Not meant to be used directly in standard log4j config */
64 class NodeLogger implements ArgeoLogger, LogListener {
65 /** Internal debug for development purposes. */
66 private static Boolean debug = false;
67
68 // private final static Log log = LogFactory.getLog(NodeLogger.class);
69
70 private Boolean disabled = false;
71
72 private String level = null;
73
74 private Level log4jLevel = null;
75 // private Layout layout;
76
77 private Properties configuration;
78
79 private AppenderImpl appender;
80
81 private final List<ArgeoLogListener> everythingListeners = Collections
82 .synchronizedList(new ArrayList<ArgeoLogListener>());
83 private final List<ArgeoLogListener> allUsersListeners = Collections
84 .synchronizedList(new ArrayList<ArgeoLogListener>());
85 private final Map<String, List<ArgeoLogListener>> userListeners = Collections
86 .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
87
88 private BlockingQueue<LogEvent> events;
89 private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
90
91 private Integer maxLastEventsCount = 10 * 1000;
92
93 /** Marker to prevent stack overflow */
94 private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
95
96 @Override
97 protected Boolean initialValue() {
98 return false;
99 }
100 };
101
102 @SuppressWarnings("unchecked")
103 public NodeLogger(LogReaderService lrs) {
104 Enumeration<LogEntry> logEntries = lrs.getLog();
105 while (logEntries.hasMoreElements())
106 logged(logEntries.nextElement());
107 lrs.addLogListener(this);
108
109 // configure log4j watcher
110 String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration");
111 if (log4jConfiguration != null && log4jConfiguration.startsWith("file:")) {
112 if (log4jConfiguration.contains("..")) {
113 if (log4jConfiguration.startsWith("file://"))
114 log4jConfiguration = log4jConfiguration.substring("file://".length());
115 else if (log4jConfiguration.startsWith("file:"))
116 log4jConfiguration = log4jConfiguration.substring("file:".length());
117 }
118 try {
119 Path log4jconfigPath;
120 if (log4jConfiguration.startsWith("file:"))
121 log4jconfigPath = Paths.get(new URI(log4jConfiguration));
122 else
123 log4jconfigPath = Paths.get(log4jConfiguration);
124 Thread log4jConfWatcher = new Log4jConfWatcherThread(log4jconfigPath);
125 log4jConfWatcher.start();
126 } catch (Exception e) {
127 stdErr("Badly formatted log4j configuration URI " + log4jConfiguration + ": " + e.getMessage());
128 }
129 }
130 }
131
132 public void init() {
133 try {
134 events = new LinkedBlockingQueue<LogEvent>();
135
136 // if (layout != null)
137 // setLayout(layout);
138 // else
139 // setLayout(new PatternLayout(pattern));
140 appender = new AppenderImpl();
141 reloadConfiguration();
142 Logger.getRootLogger().addAppender(appender);
143
144 logDispatcherThread = new LogDispatcherThread();
145 logDispatcherThread.start();
146 } catch (Exception e) {
147 throw new CmsException("Cannot initialize log4j");
148 }
149 }
150
151 public void destroy() throws Exception {
152 Logger.getRootLogger().removeAppender(appender);
153 allUsersListeners.clear();
154 for (List<ArgeoLogListener> lst : userListeners.values())
155 lst.clear();
156 userListeners.clear();
157
158 events.clear();
159 events = null;
160 logDispatcherThread.interrupt();
161 }
162
163 // public void setLayout(Layout layout) {
164 // this.layout = layout;
165 // }
166
167 public String toString() {
168 return "Node Logger";
169 }
170
171 //
172 // OSGi LOGGER
173 //
174 @Override
175 public void logged(LogEntry status) {
176 Log pluginLog = LogFactory.getLog(status.getBundle().getSymbolicName());
177 Integer severity = status.getLevel();
178 if (severity == LogService.LOG_ERROR) {
179 // FIXME Fix Argeo TP
180 if (status.getException() instanceof SignatureException)
181 return;
182 pluginLog.error(msg(status), status.getException());
183 } else if (severity == LogService.LOG_WARNING)
184 pluginLog.warn(msg(status), status.getException());
185 else if (severity == LogService.LOG_INFO && pluginLog.isDebugEnabled())
186 pluginLog.debug(msg(status), status.getException());
187 else if (severity == LogService.LOG_DEBUG && pluginLog.isTraceEnabled())
188 pluginLog.trace(msg(status), status.getException());
189 }
190
191 private String msg(LogEntry status) {
192 StringBuilder sb = new StringBuilder();
193 sb.append(status.getMessage());
194 ServiceReference<?> sr = status.getServiceReference();
195 if (sr != null) {
196 sb.append(' ');
197 String[] objectClasses = (String[]) sr.getProperty(Constants.OBJECTCLASS);
198 if (isSpringApplicationContext(objectClasses)) {
199 sb.append("{org.springframework.context.ApplicationContext}");
200 Object symbolicName = sr.getProperty(Constants.BUNDLE_SYMBOLICNAME);
201 if (symbolicName != null)
202 sb.append(" " + Constants.BUNDLE_SYMBOLICNAME + ": " + symbolicName);
203 } else {
204 sb.append(arrayToString(objectClasses));
205 }
206 Object cn = sr.getProperty(NodeConstants.CN);
207 if (cn != null)
208 sb.append(" " + NodeConstants.CN + ": " + cn);
209 Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID);
210 if (factoryPid != null)
211 sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid);
212 // else {
213 // Object servicePid = sr.getProperty(Constants.SERVICE_PID);
214 // if (servicePid != null)
215 // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
216 // }
217 // servlets
218 Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP);
219 if (whiteBoardPattern != null) {
220 if (whiteBoardPattern instanceof String) {
221 sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
222 } else {
223 sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
224 + arrayToString((String[]) whiteBoardPattern));
225 }
226 }
227 // RWT
228 Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP);
229 if (contextName != null)
230 sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
231
232 // user directories
233 Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
234 if (baseDn != null)
235 sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
236
237 }
238 return sb.toString();
239 }
240
241 private String arrayToString(Object[] arr) {
242 StringBuilder sb = new StringBuilder();
243 sb.append('[');
244 for (int i = 0; i < arr.length; i++) {
245 if (i != 0)
246 sb.append(',');
247 sb.append(arr[i]);
248 }
249 sb.append(']');
250 return sb.toString();
251 }
252
253 private boolean isSpringApplicationContext(String[] objectClasses) {
254 for (String clss : objectClasses) {
255 if (clss.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) {
256 return true;
257 }
258 }
259 return false;
260 }
261
262 //
263 // ARGEO LOGGER
264 //
265
266 public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
267 String username = CurrentUser.getUsername();
268 if (username == null)
269 throw new CmsException("Only authenticated users can register a log listener");
270
271 if (!userListeners.containsKey(username)) {
272 List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
273 userListeners.put(username, lst);
274 }
275 userListeners.get(username).add(listener);
276 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
277 for (LogEvent evt : lastEvents)
278 dispatchEvent(listener, evt);
279 }
280
281 public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
282 boolean everything) {
283 if (everything)
284 everythingListeners.add(listener);
285 else
286 allUsersListeners.add(listener);
287 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
288 for (LogEvent evt : lastEvents)
289 if (everything || evt.getUsername() != null)
290 dispatchEvent(listener, evt);
291 }
292
293 public synchronized void unregister(ArgeoLogListener listener) {
294 String username = CurrentUser.getUsername();
295 if (username == null)// FIXME
296 return;
297 if (!userListeners.containsKey(username))
298 throw new CmsException("No user listeners " + listener + " registered for user " + username);
299 if (!userListeners.get(username).contains(listener))
300 throw new CmsException("No user listeners " + listener + " registered for user " + username);
301 userListeners.get(username).remove(listener);
302 if (userListeners.get(username).isEmpty())
303 userListeners.remove(username);
304
305 }
306
307 public synchronized void unregisterForAll(ArgeoLogListener listener) {
308 everythingListeners.remove(listener);
309 allUsersListeners.remove(listener);
310 }
311
312 /** For development purpose, since using regular logging is not easy here */
313 private static void stdOut(Object obj) {
314 System.out.println(obj);
315 }
316
317 private static void stdErr(Object obj) {
318 System.err.println(obj);
319 }
320
321 private static void debug(Object obj) {
322 if (debug)
323 System.out.println(obj);
324 }
325
326 private static boolean isInternalDebugEnabled() {
327 return debug;
328 }
329
330 // public void setPattern(String pattern) {
331 // this.pattern = pattern;
332 // }
333
334 public void setDisabled(Boolean disabled) {
335 this.disabled = disabled;
336 }
337
338 public void setLevel(String level) {
339 this.level = level;
340 }
341
342 public void setConfiguration(Properties configuration) {
343 this.configuration = configuration;
344 }
345
346 public void updateConfiguration(Properties configuration) {
347 setConfiguration(configuration);
348 reloadConfiguration();
349 }
350
351 public Properties getConfiguration() {
352 return configuration;
353 }
354
355 /**
356 * Reloads configuration (if the configuration {@link Properties} is set)
357 */
358 protected void reloadConfiguration() {
359 if (configuration != null) {
360 LogManager.resetConfiguration();
361 PropertyConfigurator.configure(configuration);
362 }
363 }
364
365 protected synchronized void processLoggingEvent(LogEvent event) {
366 if (disabled)
367 return;
368
369 if (dispatching.get())
370 return;
371
372 if (level != null && !level.trim().equals("")) {
373 if (log4jLevel == null || !log4jLevel.toString().equals(level))
374 try {
375 log4jLevel = Level.toLevel(level);
376 } catch (Exception e) {
377 System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
378 e.printStackTrace();
379 level = null;
380 }
381
382 if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
383 return;
384 }
385 }
386
387 try {
388 // admin listeners
389 Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
390 while (everythingIt.hasNext())
391 dispatchEvent(everythingIt.next(), event);
392
393 if (event.getUsername() != null) {
394 Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
395 while (allUsersIt.hasNext())
396 dispatchEvent(allUsersIt.next(), event);
397
398 if (userListeners.containsKey(event.getUsername())) {
399 Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
400 while (userIt.hasNext())
401 dispatchEvent(userIt.next(), event);
402 }
403 }
404 } catch (Exception e) {
405 stdOut("Cannot process logging event");
406 e.printStackTrace();
407 }
408 }
409
410 protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
411 LoggingEvent event = evt.getLoggingEvent();
412 logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
413 event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
414 }
415
416 private class AppenderImpl extends AppenderSkeleton {
417 public boolean requiresLayout() {
418 return false;
419 }
420
421 public void close() {
422 }
423
424 @Override
425 protected void append(LoggingEvent event) {
426 if (events != null) {
427 try {
428 String username = CurrentUser.getUsername();
429 events.put(new LogEvent(username, event));
430 } catch (InterruptedException e) {
431 // silent
432 }
433 }
434 }
435
436 }
437
438 private class LogDispatcherThread extends Thread {
439 /** encapsulated in order to simplify concurrency management */
440 private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
441
442 public LogDispatcherThread() {
443 super("Argeo Logging Dispatcher Thread");
444 }
445
446 public void run() {
447 while (events != null) {
448 try {
449 LogEvent loggingEvent = events.take();
450 processLoggingEvent(loggingEvent);
451 addLastEvent(loggingEvent);
452 } catch (InterruptedException e) {
453 if (events == null)
454 return;
455 }
456 }
457 }
458
459 protected synchronized void addLastEvent(LogEvent loggingEvent) {
460 if (lastEvents.size() >= maxLastEventsCount)
461 lastEvents.poll();
462 lastEvents.add(loggingEvent);
463 }
464
465 public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
466 LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
467 ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
468 int count = 0;
469 while (it.hasPrevious() && (count < maxCount)) {
470 LogEvent evt = it.previous();
471 if (username == null || username.equals(evt.getUsername())) {
472 evts.push(evt);
473 count++;
474 }
475 }
476 return evts;
477 }
478 }
479
480 private class LogEvent {
481 private final String username;
482 private final LoggingEvent loggingEvent;
483
484 public LogEvent(String username, LoggingEvent loggingEvent) {
485 super();
486 this.username = username;
487 this.loggingEvent = loggingEvent;
488 }
489
490 @Override
491 public int hashCode() {
492 return loggingEvent.hashCode();
493 }
494
495 @Override
496 public boolean equals(Object obj) {
497 return loggingEvent.equals(obj);
498 }
499
500 @Override
501 public String toString() {
502 return username + "@ " + loggingEvent.toString();
503 }
504
505 public String getUsername() {
506 return username;
507 }
508
509 public LoggingEvent getLoggingEvent() {
510 return loggingEvent;
511 }
512
513 }
514
515 private class Log4jConfWatcherThread extends Thread {
516 private Path log4jConfigurationPath;
517
518 public Log4jConfWatcherThread(Path log4jConfigurationPath) {
519 super("Log4j Configuration Watcher");
520 try {
521 this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
522 } catch (IOException e) {
523 this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
524 stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
525 }
526 }
527
528 public void run() {
529 Path parentDir = log4jConfigurationPath.getParent();
530 try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
531 parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
532 WatchKey wk;
533 watching: while ((wk = watchService.take()) != null) {
534 for (WatchEvent<?> event : wk.pollEvents()) {
535 final Path changed = (Path) event.context();
536 if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
537 if (isInternalDebugEnabled())
538 debug(log4jConfigurationPath + " has changed, reloading.");
539 PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
540 }
541 }
542 // reset the key
543 boolean valid = wk.reset();
544 if (!valid) {
545 break watching;
546 }
547 }
548 } catch (IOException | InterruptedException e) {
549 stdErr("Log4j configuration watcher failed: " + e.getMessage());
550 }
551 }
552 }
553 }