]> git.argeo.org Git - lgpl/argeo-commons.git/blob - NodeLogger.java
b9475446e5c50a3a04f25030f73ee84e8c9f0bc6
[lgpl/argeo-commons.git] / 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.cms.auth.CurrentUser;
42 import org.argeo.node.ArgeoLogListener;
43 import org.argeo.node.ArgeoLogger;
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(
143 status.getMessage() + (status.getServiceReference() != null ?" "+ status.getServiceReference() : ""),
144 status.getException());
145 else if (severity == LogService.LOG_DEBUG && pluginLog.isTraceEnabled())
146 pluginLog.trace(status.getMessage(), status.getException());
147 }
148
149 //
150 // ARGEO LOGGER
151 //
152
153 public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
154 String username = CurrentUser.getUsername();
155 if (username == null)
156 throw new ArgeoException("Only authenticated users can register a log listener");
157
158 if (!userListeners.containsKey(username)) {
159 List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
160 userListeners.put(username, lst);
161 }
162 userListeners.get(username).add(listener);
163 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
164 for (LogEvent evt : lastEvents)
165 dispatchEvent(listener, evt);
166 }
167
168 public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
169 boolean everything) {
170 if (everything)
171 everythingListeners.add(listener);
172 else
173 allUsersListeners.add(listener);
174 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
175 for (LogEvent evt : lastEvents)
176 if (everything || evt.getUsername() != null)
177 dispatchEvent(listener, evt);
178 }
179
180 public synchronized void unregister(ArgeoLogListener listener) {
181 String username = CurrentUser.getUsername();
182 if (username == null)// FIXME
183 return;
184 if (!userListeners.containsKey(username))
185 throw new ArgeoException("No user listeners " + listener + " registered for user " + username);
186 if (!userListeners.get(username).contains(listener))
187 throw new ArgeoException("No user listeners " + listener + " registered for user " + username);
188 userListeners.get(username).remove(listener);
189 if (userListeners.get(username).isEmpty())
190 userListeners.remove(username);
191
192 }
193
194 public synchronized void unregisterForAll(ArgeoLogListener listener) {
195 everythingListeners.remove(listener);
196 allUsersListeners.remove(listener);
197 }
198
199 /** For development purpose, since using regular logging is not easy here */
200 static void stdOut(Object obj) {
201 System.out.println(obj);
202 }
203
204 // public void setPattern(String pattern) {
205 // this.pattern = pattern;
206 // }
207
208 public void setDisabled(Boolean disabled) {
209 this.disabled = disabled;
210 }
211
212 public void setLevel(String level) {
213 this.level = level;
214 }
215
216 public void setConfiguration(Properties configuration) {
217 this.configuration = configuration;
218 }
219
220 public void updateConfiguration(Properties configuration) {
221 setConfiguration(configuration);
222 reloadConfiguration();
223 }
224
225 public Properties getConfiguration() {
226 return configuration;
227 }
228
229 /**
230 * Reloads configuration (if the configuration {@link Properties} is set)
231 */
232 protected void reloadConfiguration() {
233 if (configuration != null) {
234 LogManager.resetConfiguration();
235 PropertyConfigurator.configure(configuration);
236 }
237 }
238
239 protected synchronized void processLoggingEvent(LogEvent event) {
240 if (disabled)
241 return;
242
243 if (dispatching.get())
244 return;
245
246 if (level != null && !level.trim().equals("")) {
247 if (log4jLevel == null || !log4jLevel.toString().equals(level))
248 try {
249 log4jLevel = Level.toLevel(level);
250 } catch (Exception e) {
251 System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
252 e.printStackTrace();
253 level = null;
254 }
255
256 if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
257 return;
258 }
259 }
260
261 try {
262 // admin listeners
263 Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
264 while (everythingIt.hasNext())
265 dispatchEvent(everythingIt.next(), event);
266
267 if (event.getUsername() != null) {
268 Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
269 while (allUsersIt.hasNext())
270 dispatchEvent(allUsersIt.next(), event);
271
272 if (userListeners.containsKey(event.getUsername())) {
273 Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
274 while (userIt.hasNext())
275 dispatchEvent(userIt.next(), event);
276 }
277 }
278 } catch (Exception e) {
279 stdOut("Cannot process logging event");
280 e.printStackTrace();
281 }
282 }
283
284 protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
285 LoggingEvent event = evt.getLoggingEvent();
286 logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
287 event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
288 }
289
290 private class AppenderImpl extends AppenderSkeleton {
291 public boolean requiresLayout() {
292 return false;
293 }
294
295 public void close() {
296 }
297
298 @Override
299 protected void append(LoggingEvent event) {
300 if (events != null) {
301 try {
302 String username = CurrentUser.getUsername();
303 events.put(new LogEvent(username, event));
304 } catch (InterruptedException e) {
305 // silent
306 }
307 }
308 }
309
310 }
311
312 private class LogDispatcherThread extends Thread {
313 /** encapsulated in order to simplify concurrency management */
314 private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
315
316 public LogDispatcherThread() {
317 super("Argeo Logging Dispatcher Thread");
318 }
319
320 public void run() {
321 while (events != null) {
322 try {
323 LogEvent loggingEvent = events.take();
324 processLoggingEvent(loggingEvent);
325 addLastEvent(loggingEvent);
326 } catch (InterruptedException e) {
327 if (events == null)
328 return;
329 }
330 }
331 }
332
333 protected synchronized void addLastEvent(LogEvent loggingEvent) {
334 if (lastEvents.size() >= maxLastEventsCount)
335 lastEvents.poll();
336 lastEvents.add(loggingEvent);
337 }
338
339 public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
340 LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
341 ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
342 int count = 0;
343 while (it.hasPrevious() && (count < maxCount)) {
344 LogEvent evt = it.previous();
345 if (username == null || username.equals(evt.getUsername())) {
346 evts.push(evt);
347 count++;
348 }
349 }
350 return evts;
351 }
352 }
353
354 private class LogEvent {
355 private final String username;
356 private final LoggingEvent loggingEvent;
357
358 public LogEvent(String username, LoggingEvent loggingEvent) {
359 super();
360 this.username = username;
361 this.loggingEvent = loggingEvent;
362 }
363
364 @Override
365 public int hashCode() {
366 return loggingEvent.hashCode();
367 }
368
369 @Override
370 public boolean equals(Object obj) {
371 return loggingEvent.equals(obj);
372 }
373
374 @Override
375 public String toString() {
376 return username + "@ " + loggingEvent.toString();
377 }
378
379 public String getUsername() {
380 return username;
381 }
382
383 public LoggingEvent getLoggingEvent() {
384 return loggingEvent;
385 }
386
387 }
388 }