2 * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package org
.argeo
.security
.log4j
;
19 import java
.util
.ArrayList
;
20 import java
.util
.Collections
;
21 import java
.util
.HashMap
;
22 import java
.util
.Iterator
;
23 import java
.util
.LinkedList
;
24 import java
.util
.List
;
25 import java
.util
.ListIterator
;
27 import java
.util
.Properties
;
28 import java
.util
.concurrent
.BlockingQueue
;
29 import java
.util
.concurrent
.LinkedBlockingQueue
;
31 import org
.apache
.log4j
.AppenderSkeleton
;
32 import org
.apache
.log4j
.Level
;
33 import org
.apache
.log4j
.LogManager
;
34 import org
.apache
.log4j
.Logger
;
35 import org
.apache
.log4j
.PropertyConfigurator
;
36 import org
.apache
.log4j
.spi
.LoggingEvent
;
37 import org
.argeo
.ArgeoException
;
38 import org
.argeo
.ArgeoLogListener
;
39 import org
.argeo
.ArgeoLogger
;
40 import org
.argeo
.security
.SecurityUtils
;
42 /** Not meant to be used directly in standard log4j config */
43 public class SecureLogger
implements ArgeoLogger
{
45 private Boolean disabled
= false;
47 private String level
= null;
49 private Level log4jLevel
= null;
50 // private Layout layout;
52 private Properties configuration
;
54 private AppenderImpl appender
;
56 private final List
<ArgeoLogListener
> everythingListeners
= Collections
57 .synchronizedList(new ArrayList
<ArgeoLogListener
>());
58 private final List
<ArgeoLogListener
> allUsersListeners
= Collections
59 .synchronizedList(new ArrayList
<ArgeoLogListener
>());
60 private final Map
<String
, List
<ArgeoLogListener
>> userListeners
= Collections
61 .synchronizedMap(new HashMap
<String
, List
<ArgeoLogListener
>>());
63 private BlockingQueue
<LogEvent
> events
;
64 private LogDispatcherThread logDispatcherThread
= new LogDispatcherThread();
66 private Integer maxLastEventsCount
= 10 * 1000;
68 /** Marker to prevent stack overflow */
69 private ThreadLocal
<Boolean
> dispatching
= new ThreadLocal
<Boolean
>() {
72 protected Boolean
initialValue() {
79 events
= new LinkedBlockingQueue
<LogEvent
>();
81 // if (layout != null)
84 // setLayout(new PatternLayout(pattern));
85 appender
= new AppenderImpl();
86 reloadConfiguration();
87 Logger
.getRootLogger().addAppender(appender
);
89 logDispatcherThread
= new LogDispatcherThread();
90 logDispatcherThread
.start();
91 } catch (Exception e
) {
92 throw new ArgeoException("Cannot initialize log4j");
96 public void destroy() throws Exception
{
97 Logger
.getRootLogger().removeAppender(appender
);
98 allUsersListeners
.clear();
99 for (List
<ArgeoLogListener
> lst
: userListeners
.values())
101 userListeners
.clear();
105 logDispatcherThread
.interrupt();
108 // public void setLayout(Layout layout) {
109 // this.layout = layout;
112 public synchronized void register(ArgeoLogListener listener
,
113 Integer numberOfPreviousEvents
) {
114 String username
= SecurityUtils
.getCurrentThreadUsername();
115 if (username
== null)
116 throw new ArgeoException(
117 "Only authenticated users can register a log listener");
119 if (!userListeners
.containsKey(username
)) {
120 List
<ArgeoLogListener
> lst
= Collections
121 .synchronizedList(new ArrayList
<ArgeoLogListener
>());
122 userListeners
.put(username
, lst
);
124 userListeners
.get(username
).add(listener
);
125 List
<LogEvent
> lastEvents
= logDispatcherThread
.getLastEvents(username
,
126 numberOfPreviousEvents
);
127 for (LogEvent evt
: lastEvents
)
128 dispatchEvent(listener
, evt
);
131 public synchronized void registerForAll(ArgeoLogListener listener
,
132 Integer numberOfPreviousEvents
, boolean everything
) {
134 everythingListeners
.add(listener
);
136 allUsersListeners
.add(listener
);
137 List
<LogEvent
> lastEvents
= logDispatcherThread
.getLastEvents(null,
138 numberOfPreviousEvents
);
139 for (LogEvent evt
: lastEvents
)
140 if (everything
|| evt
.getUsername() != null)
141 dispatchEvent(listener
, evt
);
144 public synchronized void unregister(ArgeoLogListener listener
) {
145 String username
= SecurityUtils
.getCurrentThreadUsername();
146 if (!userListeners
.containsKey(username
))
147 throw new ArgeoException("No user listeners " + listener
148 + " registered for user " + username
);
149 if (!userListeners
.get(username
).contains(listener
))
150 throw new ArgeoException("No user listeners " + listener
151 + " registered for user " + username
);
152 userListeners
.get(username
).remove(listener
);
153 if (userListeners
.get(username
).isEmpty())
154 userListeners
.remove(username
);
158 public synchronized void unregisterForAll(ArgeoLogListener listener
) {
159 everythingListeners
.remove(listener
);
160 allUsersListeners
.remove(listener
);
163 /** For development purpose, since using regular logging is not easy here */
164 static void stdOut(Object obj
) {
165 System
.out
.println(obj
);
168 // public void setPattern(String pattern) {
169 // this.pattern = pattern;
172 public void setDisabled(Boolean disabled
) {
173 this.disabled
= disabled
;
176 public void setLevel(String level
) {
180 public void setConfiguration(Properties configuration
) {
181 this.configuration
= configuration
;
184 public void updateConfiguration(Properties configuration
) {
185 setConfiguration(configuration
);
186 reloadConfiguration();
189 public Properties
getConfiguration() {
190 return configuration
;
193 /** Reloads configuration (if the configuration {@link Properties} is set) */
194 protected void reloadConfiguration() {
195 if (configuration
!= null) {
196 LogManager
.resetConfiguration();
197 PropertyConfigurator
.configure(configuration
);
201 protected synchronized void processLoggingEvent(LogEvent event
) {
205 if (dispatching
.get())
208 if (level
!= null && !level
.trim().equals("")) {
209 if (log4jLevel
== null || !log4jLevel
.toString().equals(level
))
211 log4jLevel
= Level
.toLevel(level
);
212 } catch (Exception e
) {
214 .println("Log4j level could not be set for level '"
215 + level
+ "', resetting it to null.");
220 if (log4jLevel
!= null
221 && !event
.getLoggingEvent().getLevel()
222 .isGreaterOrEqual(log4jLevel
)) {
229 Iterator
<ArgeoLogListener
> everythingIt
= everythingListeners
231 while (everythingIt
.hasNext())
232 dispatchEvent(everythingIt
.next(), event
);
234 if (event
.getUsername() != null) {
235 Iterator
<ArgeoLogListener
> allUsersIt
= allUsersListeners
237 while (allUsersIt
.hasNext())
238 dispatchEvent(allUsersIt
.next(), event
);
240 if (userListeners
.containsKey(event
.getUsername())) {
241 Iterator
<ArgeoLogListener
> userIt
= userListeners
.get(
242 event
.getUsername()).iterator();
243 while (userIt
.hasNext())
244 dispatchEvent(userIt
.next(), event
);
247 } catch (Exception e
) {
248 stdOut("Cannot process logging event");
253 protected void dispatchEvent(ArgeoLogListener logListener
, LogEvent evt
) {
254 LoggingEvent event
= evt
.getLoggingEvent();
255 logListener
.appendLog(evt
.getUsername(), event
.getTimeStamp(), event
256 .getLevel().toString(), event
.getLoggerName(), event
257 .getThreadName(), event
.getMessage(), event
258 .getThrowableStrRep());
261 private class AppenderImpl
extends AppenderSkeleton
{
262 public boolean requiresLayout() {
266 public void close() {
270 protected void append(LoggingEvent event
) {
271 if (events
!= null) {
273 String username
= SecurityUtils
.getCurrentThreadUsername();
274 events
.put(new LogEvent(username
, event
));
275 } catch (InterruptedException e
) {
283 private class LogDispatcherThread
extends Thread
{
284 /** encapsulated in order to simplify concurrency management */
285 private LinkedList
<LogEvent
> lastEvents
= new LinkedList
<LogEvent
>();
287 public LogDispatcherThread() {
288 super("Argeo Logging Dispatcher Thread");
292 while (events
!= null) {
294 LogEvent loggingEvent
= events
.take();
295 processLoggingEvent(loggingEvent
);
296 addLastEvent(loggingEvent
);
297 } catch (InterruptedException e
) {
304 protected synchronized void addLastEvent(LogEvent loggingEvent
) {
305 if (lastEvents
.size() >= maxLastEventsCount
)
307 lastEvents
.add(loggingEvent
);
310 public synchronized List
<LogEvent
> getLastEvents(String username
,
312 LinkedList
<LogEvent
> evts
= new LinkedList
<LogEvent
>();
313 ListIterator
<LogEvent
> it
= lastEvents
.listIterator(lastEvents
316 while (it
.hasPrevious() && (count
< maxCount
)) {
317 LogEvent evt
= it
.previous();
318 if (username
== null || username
.equals(evt
.getUsername())) {
327 private class LogEvent
{
328 private final String username
;
329 private final LoggingEvent loggingEvent
;
331 public LogEvent(String username
, LoggingEvent loggingEvent
) {
333 this.username
= username
;
334 this.loggingEvent
= loggingEvent
;
338 public int hashCode() {
339 return loggingEvent
.hashCode();
343 public boolean equals(Object obj
) {
344 return loggingEvent
.equals(obj
);
348 public String
toString() {
349 return username
+ "@ " + loggingEvent
.toString();
352 public String
getUsername() {
356 public LoggingEvent
getLoggingEvent() {