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