]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java
Introduce Argeo Sync
[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.Bundle;
56 import org.osgi.framework.Constants;
57 import org.osgi.framework.ServiceReference;
58 import org.osgi.service.cm.ConfigurationAdmin;
59 import org.osgi.service.log.LogEntry;
60 import org.osgi.service.log.LogListener;
61 import org.osgi.service.log.LogReaderService;
62 import org.osgi.service.log.LogService;
63
64 /** Not meant to be used directly in standard log4j config */
65 class NodeLogger implements ArgeoLogger, LogListener {
66 /** Internal debug for development purposes. */
67 private static Boolean debug = false;
68
69 // private final static Log log = LogFactory.getLog(NodeLogger.class);
70
71 private Boolean disabled = false;
72
73 private String level = null;
74
75 private Level log4jLevel = null;
76 // private Layout layout;
77
78 private Properties configuration;
79
80 private AppenderImpl appender;
81
82 private final List<ArgeoLogListener> everythingListeners = Collections
83 .synchronizedList(new ArrayList<ArgeoLogListener>());
84 private final List<ArgeoLogListener> allUsersListeners = Collections
85 .synchronizedList(new ArrayList<ArgeoLogListener>());
86 private final Map<String, List<ArgeoLogListener>> userListeners = Collections
87 .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
88
89 private BlockingQueue<LogEvent> events;
90 private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
91
92 private Integer maxLastEventsCount = 10 * 1000;
93
94 /** Marker to prevent stack overflow */
95 private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
96
97 @Override
98 protected Boolean initialValue() {
99 return false;
100 }
101 };
102
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 @SuppressWarnings("deprecation")
175 @Override
176 public void logged(LogEntry status) {
177 Log pluginLog = LogFactory.getLog(status.getBundle().getSymbolicName());
178 Integer severity = status.getLevel();
179 if (severity == LogService.LOG_ERROR) {
180 // FIXME Fix Argeo TP
181 if (status.getException() instanceof SignatureException)
182 return;
183 // pluginLog.error(msg(status), status.getException());
184 pluginLog.error(msg(status) + ": " + status.getException());
185 } else if (severity == LogService.LOG_WARNING)
186 pluginLog.warn(msg(status), status.getException());
187 else if (severity == LogService.LOG_INFO && pluginLog.isDebugEnabled())
188 pluginLog.debug(msg(status), status.getException());
189 else if (severity == LogService.LOG_DEBUG && pluginLog.isTraceEnabled())
190 pluginLog.trace(msg(status), status.getException());
191 }
192
193 private String msg(LogEntry status) {
194 StringBuilder sb = new StringBuilder();
195 sb.append(status.getMessage());
196 Bundle bundle = status.getBundle();
197 if (bundle != null) {
198 sb.append(" '" + bundle.getSymbolicName() + "'");
199 }
200 ServiceReference<?> sr = status.getServiceReference();
201 if (sr != null) {
202 sb.append(' ');
203 String[] objectClasses = (String[]) sr.getProperty(Constants.OBJECTCLASS);
204 if (isSpringApplicationContext(objectClasses)) {
205 sb.append("{org.springframework.context.ApplicationContext}");
206 Object symbolicName = sr.getProperty(Constants.BUNDLE_SYMBOLICNAME);
207 if (symbolicName != null)
208 sb.append(" " + Constants.BUNDLE_SYMBOLICNAME + ": " + symbolicName);
209 } else {
210 sb.append(arrayToString(objectClasses));
211 }
212 Object cn = sr.getProperty(NodeConstants.CN);
213 if (cn != null)
214 sb.append(" " + NodeConstants.CN + ": " + cn);
215 Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID);
216 if (factoryPid != null)
217 sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid);
218 // else {
219 // Object servicePid = sr.getProperty(Constants.SERVICE_PID);
220 // if (servicePid != null)
221 // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
222 // }
223 // servlets
224 Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP);
225 if (whiteBoardPattern != null) {
226 if (whiteBoardPattern instanceof String) {
227 sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
228 } else {
229 sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
230 + arrayToString((String[]) whiteBoardPattern));
231 }
232 }
233 // RWT
234 Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP);
235 if (contextName != null)
236 sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
237
238 // user directories
239 Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
240 if (baseDn != null)
241 sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
242
243 }
244 return sb.toString();
245 }
246
247 private String arrayToString(Object[] arr) {
248 StringBuilder sb = new StringBuilder();
249 sb.append('[');
250 for (int i = 0; i < arr.length; i++) {
251 if (i != 0)
252 sb.append(',');
253 sb.append(arr[i]);
254 }
255 sb.append(']');
256 return sb.toString();
257 }
258
259 private boolean isSpringApplicationContext(String[] objectClasses) {
260 for (String clss : objectClasses) {
261 if (clss.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) {
262 return true;
263 }
264 }
265 return false;
266 }
267
268 //
269 // ARGEO LOGGER
270 //
271
272 public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
273 String username = CurrentUser.getUsername();
274 if (username == null)
275 throw new CmsException("Only authenticated users can register a log listener");
276
277 if (!userListeners.containsKey(username)) {
278 List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
279 userListeners.put(username, lst);
280 }
281 userListeners.get(username).add(listener);
282 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
283 for (LogEvent evt : lastEvents)
284 dispatchEvent(listener, evt);
285 }
286
287 public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
288 boolean everything) {
289 if (everything)
290 everythingListeners.add(listener);
291 else
292 allUsersListeners.add(listener);
293 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
294 for (LogEvent evt : lastEvents)
295 if (everything || evt.getUsername() != null)
296 dispatchEvent(listener, evt);
297 }
298
299 public synchronized void unregister(ArgeoLogListener listener) {
300 String username = CurrentUser.getUsername();
301 if (username == null)// FIXME
302 return;
303 if (!userListeners.containsKey(username))
304 throw new CmsException("No user listeners " + listener + " registered for user " + username);
305 if (!userListeners.get(username).contains(listener))
306 throw new CmsException("No user listeners " + listener + " registered for user " + username);
307 userListeners.get(username).remove(listener);
308 if (userListeners.get(username).isEmpty())
309 userListeners.remove(username);
310
311 }
312
313 public synchronized void unregisterForAll(ArgeoLogListener listener) {
314 everythingListeners.remove(listener);
315 allUsersListeners.remove(listener);
316 }
317
318 /** For development purpose, since using regular logging is not easy here */
319 private static void stdOut(Object obj) {
320 System.out.println(obj);
321 }
322
323 private static void stdErr(Object obj) {
324 System.err.println(obj);
325 }
326
327 private static void debug(Object obj) {
328 if (debug)
329 System.out.println(obj);
330 }
331
332 private static boolean isInternalDebugEnabled() {
333 return debug;
334 }
335
336 // public void setPattern(String pattern) {
337 // this.pattern = pattern;
338 // }
339
340 public void setDisabled(Boolean disabled) {
341 this.disabled = disabled;
342 }
343
344 public void setLevel(String level) {
345 this.level = level;
346 }
347
348 public void setConfiguration(Properties configuration) {
349 this.configuration = configuration;
350 }
351
352 public void updateConfiguration(Properties configuration) {
353 setConfiguration(configuration);
354 reloadConfiguration();
355 }
356
357 public Properties getConfiguration() {
358 return configuration;
359 }
360
361 /**
362 * Reloads configuration (if the configuration {@link Properties} is set)
363 */
364 protected void reloadConfiguration() {
365 if (configuration != null) {
366 LogManager.resetConfiguration();
367 PropertyConfigurator.configure(configuration);
368 }
369 }
370
371 protected synchronized void processLoggingEvent(LogEvent event) {
372 if (disabled)
373 return;
374
375 if (dispatching.get())
376 return;
377
378 if (level != null && !level.trim().equals("")) {
379 if (log4jLevel == null || !log4jLevel.toString().equals(level))
380 try {
381 log4jLevel = Level.toLevel(level);
382 } catch (Exception e) {
383 System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
384 e.printStackTrace();
385 level = null;
386 }
387
388 if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
389 return;
390 }
391 }
392
393 try {
394 // admin listeners
395 Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
396 while (everythingIt.hasNext())
397 dispatchEvent(everythingIt.next(), event);
398
399 if (event.getUsername() != null) {
400 Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
401 while (allUsersIt.hasNext())
402 dispatchEvent(allUsersIt.next(), event);
403
404 if (userListeners.containsKey(event.getUsername())) {
405 Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
406 while (userIt.hasNext())
407 dispatchEvent(userIt.next(), event);
408 }
409 }
410 } catch (Exception e) {
411 stdOut("Cannot process logging event");
412 e.printStackTrace();
413 }
414 }
415
416 protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
417 LoggingEvent event = evt.getLoggingEvent();
418 logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
419 event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
420 }
421
422 private class AppenderImpl extends AppenderSkeleton {
423 public boolean requiresLayout() {
424 return false;
425 }
426
427 public void close() {
428 }
429
430 @Override
431 protected void append(LoggingEvent event) {
432 if (events != null) {
433 try {
434 String username = CurrentUser.getUsername();
435 events.put(new LogEvent(username, event));
436 } catch (InterruptedException e) {
437 // silent
438 }
439 }
440 }
441
442 }
443
444 private class LogDispatcherThread extends Thread {
445 /** encapsulated in order to simplify concurrency management */
446 private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
447
448 public LogDispatcherThread() {
449 super("Argeo Logging Dispatcher Thread");
450 }
451
452 public void run() {
453 while (events != null) {
454 try {
455 LogEvent loggingEvent = events.take();
456 processLoggingEvent(loggingEvent);
457 addLastEvent(loggingEvent);
458 } catch (InterruptedException e) {
459 if (events == null)
460 return;
461 }
462 }
463 }
464
465 protected synchronized void addLastEvent(LogEvent loggingEvent) {
466 if (lastEvents.size() >= maxLastEventsCount)
467 lastEvents.poll();
468 lastEvents.add(loggingEvent);
469 }
470
471 public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
472 LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
473 ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
474 int count = 0;
475 while (it.hasPrevious() && (count < maxCount)) {
476 LogEvent evt = it.previous();
477 if (username == null || username.equals(evt.getUsername())) {
478 evts.push(evt);
479 count++;
480 }
481 }
482 return evts;
483 }
484 }
485
486 private class LogEvent {
487 private final String username;
488 private final LoggingEvent loggingEvent;
489
490 public LogEvent(String username, LoggingEvent loggingEvent) {
491 super();
492 this.username = username;
493 this.loggingEvent = loggingEvent;
494 }
495
496 @Override
497 public int hashCode() {
498 return loggingEvent.hashCode();
499 }
500
501 @Override
502 public boolean equals(Object obj) {
503 return loggingEvent.equals(obj);
504 }
505
506 @Override
507 public String toString() {
508 return username + "@ " + loggingEvent.toString();
509 }
510
511 public String getUsername() {
512 return username;
513 }
514
515 public LoggingEvent getLoggingEvent() {
516 return loggingEvent;
517 }
518
519 }
520
521 private class Log4jConfWatcherThread extends Thread {
522 private Path log4jConfigurationPath;
523
524 public Log4jConfWatcherThread(Path log4jConfigurationPath) {
525 super("Log4j Configuration Watcher");
526 try {
527 this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
528 } catch (IOException e) {
529 this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
530 stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
531 }
532 }
533
534 public void run() {
535 Path parentDir = log4jConfigurationPath.getParent();
536 try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
537 parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
538 WatchKey wk;
539 watching: while ((wk = watchService.take()) != null) {
540 for (WatchEvent<?> event : wk.pollEvents()) {
541 final Path changed = (Path) event.context();
542 if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
543 if (isInternalDebugEnabled())
544 debug(log4jConfigurationPath + " has changed, reloading.");
545 PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
546 }
547 }
548 // reset the key
549 boolean valid = wk.reset();
550 if (!valid) {
551 break watching;
552 }
553 }
554 } catch (IOException | InterruptedException e) {
555 stdErr("Log4j configuration watcher failed: " + e.getMessage());
556 }
557 }
558 }
559 }