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