]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java
134ab26ab3d06b8f3a97bd81bc357b48dd11c342
[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.security.SignatureException;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.Enumeration;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.ListIterator;
27 import java.util.Map;
28 import java.util.Properties;
29 import java.util.concurrent.BlockingQueue;
30 import java.util.concurrent.LinkedBlockingQueue;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.apache.log4j.AppenderSkeleton;
35 import org.apache.log4j.Level;
36 import org.apache.log4j.LogManager;
37 import org.apache.log4j.Logger;
38 import org.apache.log4j.PropertyConfigurator;
39 import org.apache.log4j.spi.LoggingEvent;
40 import org.argeo.cms.CmsException;
41 import org.argeo.cms.auth.CurrentUser;
42 import org.argeo.node.ArgeoLogListener;
43 import org.argeo.node.ArgeoLogger;
44 import org.argeo.node.NodeConstants;
45 import org.argeo.osgi.useradmin.UserAdminConf;
46 import org.osgi.framework.Constants;
47 import org.osgi.framework.ServiceReference;
48 import org.osgi.service.cm.ConfigurationAdmin;
49 import org.osgi.service.log.LogEntry;
50 import org.osgi.service.log.LogListener;
51 import org.osgi.service.log.LogReaderService;
52 import org.osgi.service.log.LogService;
53
54 /** Not meant to be used directly in standard log4j config */
55 class NodeLogger implements ArgeoLogger, LogListener {
56
57 private Boolean disabled = false;
58
59 private String level = null;
60
61 private Level log4jLevel = null;
62 // private Layout layout;
63
64 private Properties configuration;
65
66 private AppenderImpl appender;
67
68 private final List<ArgeoLogListener> everythingListeners = Collections
69 .synchronizedList(new ArrayList<ArgeoLogListener>());
70 private final List<ArgeoLogListener> allUsersListeners = Collections
71 .synchronizedList(new ArrayList<ArgeoLogListener>());
72 private final Map<String, List<ArgeoLogListener>> userListeners = Collections
73 .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
74
75 private BlockingQueue<LogEvent> events;
76 private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
77
78 private Integer maxLastEventsCount = 10 * 1000;
79
80 /** Marker to prevent stack overflow */
81 private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
82
83 @Override
84 protected Boolean initialValue() {
85 return false;
86 }
87 };
88
89 @SuppressWarnings("unchecked")
90 public NodeLogger(LogReaderService lrs) {
91 Enumeration<LogEntry> logEntries = lrs.getLog();
92 while (logEntries.hasMoreElements())
93 logged(logEntries.nextElement());
94 lrs.addLogListener(this);
95 }
96
97 public void init() {
98 try {
99 events = new LinkedBlockingQueue<LogEvent>();
100
101 // if (layout != null)
102 // setLayout(layout);
103 // else
104 // setLayout(new PatternLayout(pattern));
105 appender = new AppenderImpl();
106 reloadConfiguration();
107 Logger.getRootLogger().addAppender(appender);
108
109 logDispatcherThread = new LogDispatcherThread();
110 logDispatcherThread.start();
111 } catch (Exception e) {
112 throw new CmsException("Cannot initialize log4j");
113 }
114 }
115
116 public void destroy() throws Exception {
117 Logger.getRootLogger().removeAppender(appender);
118 allUsersListeners.clear();
119 for (List<ArgeoLogListener> lst : userListeners.values())
120 lst.clear();
121 userListeners.clear();
122
123 events.clear();
124 events = null;
125 logDispatcherThread.interrupt();
126 }
127
128 // public void setLayout(Layout layout) {
129 // this.layout = layout;
130 // }
131
132 public String toString() {
133 return "Node Logger";
134 }
135
136 //
137 // OSGi LOGGER
138 //
139 @Override
140 public void logged(LogEntry status) {
141 Log pluginLog = LogFactory.getLog(status.getBundle().getSymbolicName());
142 Integer severity = status.getLevel();
143 if (severity == LogService.LOG_ERROR) {
144 // FIXME Fix Argeo TP
145 if (status.getException() instanceof SignatureException)
146 return;
147 pluginLog.error(msg(status), status.getException());
148 } else if (severity == LogService.LOG_WARNING)
149 pluginLog.warn(msg(status), status.getException());
150 else if (severity == LogService.LOG_INFO && pluginLog.isDebugEnabled())
151 pluginLog.debug(msg(status), status.getException());
152 else if (severity == LogService.LOG_DEBUG && pluginLog.isTraceEnabled())
153 pluginLog.trace(msg(status), status.getException());
154 }
155
156 private String msg(LogEntry status) {
157 StringBuilder sb = new StringBuilder();
158 sb.append(status.getMessage());
159 ServiceReference<?> sr = status.getServiceReference();
160 if (sr != null) {
161 sb.append(' ');
162 String[] objectClasses = (String[]) sr.getProperty(Constants.OBJECTCLASS);
163 if (isSpringApplicationContext(objectClasses)) {
164 sb.append("{org.springframework.context.ApplicationContext}");
165 Object symbolicName = sr.getProperty(Constants.BUNDLE_SYMBOLICNAME);
166 if (symbolicName != null)
167 sb.append(" " + Constants.BUNDLE_SYMBOLICNAME + ": " + symbolicName);
168 } else {
169 sb.append(arrayToString(objectClasses));
170 }
171 Object cn = sr.getProperty(NodeConstants.CN);
172 if (cn != null)
173 sb.append(" " + NodeConstants.CN + ": " + cn);
174 Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID);
175 if (factoryPid != null)
176 sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid);
177 // else {
178 // Object servicePid = sr.getProperty(Constants.SERVICE_PID);
179 // if (servicePid != null)
180 // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
181 // }
182 // servlets
183 Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP);
184 if (whiteBoardPattern != null) {
185 if (whiteBoardPattern instanceof String) {
186 sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
187 } else {
188 sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
189 + arrayToString((String[]) whiteBoardPattern));
190 }
191 }
192 // RWT
193 Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP);
194 if (contextName != null)
195 sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
196
197 // user directories
198 Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
199 if (baseDn != null)
200 sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
201
202 }
203 return sb.toString();
204 }
205
206 private String arrayToString(Object[] arr) {
207 StringBuilder sb = new StringBuilder();
208 sb.append('[');
209 for (int i = 0; i < arr.length; i++) {
210 if (i != 0)
211 sb.append(',');
212 sb.append(arr[i]);
213 }
214 sb.append(']');
215 return sb.toString();
216 }
217
218 private boolean isSpringApplicationContext(String[] objectClasses) {
219 for (String clss : objectClasses) {
220 if (clss.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) {
221 return true;
222 }
223 }
224 return false;
225 }
226
227 //
228 // ARGEO LOGGER
229 //
230
231 public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
232 String username = CurrentUser.getUsername();
233 if (username == null)
234 throw new CmsException("Only authenticated users can register a log listener");
235
236 if (!userListeners.containsKey(username)) {
237 List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
238 userListeners.put(username, lst);
239 }
240 userListeners.get(username).add(listener);
241 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
242 for (LogEvent evt : lastEvents)
243 dispatchEvent(listener, evt);
244 }
245
246 public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
247 boolean everything) {
248 if (everything)
249 everythingListeners.add(listener);
250 else
251 allUsersListeners.add(listener);
252 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
253 for (LogEvent evt : lastEvents)
254 if (everything || evt.getUsername() != null)
255 dispatchEvent(listener, evt);
256 }
257
258 public synchronized void unregister(ArgeoLogListener listener) {
259 String username = CurrentUser.getUsername();
260 if (username == null)// FIXME
261 return;
262 if (!userListeners.containsKey(username))
263 throw new CmsException("No user listeners " + listener + " registered for user " + username);
264 if (!userListeners.get(username).contains(listener))
265 throw new CmsException("No user listeners " + listener + " registered for user " + username);
266 userListeners.get(username).remove(listener);
267 if (userListeners.get(username).isEmpty())
268 userListeners.remove(username);
269
270 }
271
272 public synchronized void unregisterForAll(ArgeoLogListener listener) {
273 everythingListeners.remove(listener);
274 allUsersListeners.remove(listener);
275 }
276
277 /** For development purpose, since using regular logging is not easy here */
278 static void stdOut(Object obj) {
279 System.out.println(obj);
280 }
281
282 // public void setPattern(String pattern) {
283 // this.pattern = pattern;
284 // }
285
286 public void setDisabled(Boolean disabled) {
287 this.disabled = disabled;
288 }
289
290 public void setLevel(String level) {
291 this.level = level;
292 }
293
294 public void setConfiguration(Properties configuration) {
295 this.configuration = configuration;
296 }
297
298 public void updateConfiguration(Properties configuration) {
299 setConfiguration(configuration);
300 reloadConfiguration();
301 }
302
303 public Properties getConfiguration() {
304 return configuration;
305 }
306
307 /**
308 * Reloads configuration (if the configuration {@link Properties} is set)
309 */
310 protected void reloadConfiguration() {
311 if (configuration != null) {
312 LogManager.resetConfiguration();
313 PropertyConfigurator.configure(configuration);
314 }
315 }
316
317 protected synchronized void processLoggingEvent(LogEvent event) {
318 if (disabled)
319 return;
320
321 if (dispatching.get())
322 return;
323
324 if (level != null && !level.trim().equals("")) {
325 if (log4jLevel == null || !log4jLevel.toString().equals(level))
326 try {
327 log4jLevel = Level.toLevel(level);
328 } catch (Exception e) {
329 System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
330 e.printStackTrace();
331 level = null;
332 }
333
334 if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
335 return;
336 }
337 }
338
339 try {
340 // admin listeners
341 Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
342 while (everythingIt.hasNext())
343 dispatchEvent(everythingIt.next(), event);
344
345 if (event.getUsername() != null) {
346 Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
347 while (allUsersIt.hasNext())
348 dispatchEvent(allUsersIt.next(), event);
349
350 if (userListeners.containsKey(event.getUsername())) {
351 Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
352 while (userIt.hasNext())
353 dispatchEvent(userIt.next(), event);
354 }
355 }
356 } catch (Exception e) {
357 stdOut("Cannot process logging event");
358 e.printStackTrace();
359 }
360 }
361
362 protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
363 LoggingEvent event = evt.getLoggingEvent();
364 logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
365 event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
366 }
367
368 private class AppenderImpl extends AppenderSkeleton {
369 public boolean requiresLayout() {
370 return false;
371 }
372
373 public void close() {
374 }
375
376 @Override
377 protected void append(LoggingEvent event) {
378 if (events != null) {
379 try {
380 String username = CurrentUser.getUsername();
381 events.put(new LogEvent(username, event));
382 } catch (InterruptedException e) {
383 // silent
384 }
385 }
386 }
387
388 }
389
390 private class LogDispatcherThread extends Thread {
391 /** encapsulated in order to simplify concurrency management */
392 private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
393
394 public LogDispatcherThread() {
395 super("Argeo Logging Dispatcher Thread");
396 }
397
398 public void run() {
399 while (events != null) {
400 try {
401 LogEvent loggingEvent = events.take();
402 processLoggingEvent(loggingEvent);
403 addLastEvent(loggingEvent);
404 } catch (InterruptedException e) {
405 if (events == null)
406 return;
407 }
408 }
409 }
410
411 protected synchronized void addLastEvent(LogEvent loggingEvent) {
412 if (lastEvents.size() >= maxLastEventsCount)
413 lastEvents.poll();
414 lastEvents.add(loggingEvent);
415 }
416
417 public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
418 LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
419 ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
420 int count = 0;
421 while (it.hasPrevious() && (count < maxCount)) {
422 LogEvent evt = it.previous();
423 if (username == null || username.equals(evt.getUsername())) {
424 evts.push(evt);
425 count++;
426 }
427 }
428 return evts;
429 }
430 }
431
432 private class LogEvent {
433 private final String username;
434 private final LoggingEvent loggingEvent;
435
436 public LogEvent(String username, LoggingEvent loggingEvent) {
437 super();
438 this.username = username;
439 this.loggingEvent = loggingEvent;
440 }
441
442 @Override
443 public int hashCode() {
444 return loggingEvent.hashCode();
445 }
446
447 @Override
448 public boolean equals(Object obj) {
449 return loggingEvent.equals(obj);
450 }
451
452 @Override
453 public String toString() {
454 return username + "@ " + loggingEvent.toString();
455 }
456
457 public String getUsername() {
458 return username;
459 }
460
461 public LoggingEvent getLoggingEvent() {
462 return loggingEvent;
463 }
464
465 }
466 }