001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.extensions.requestlogger;
018
019import java.io.IOException;
020import java.io.Serializable;
021
022import com.fasterxml.jackson.databind.ObjectMapper;
023import com.fasterxml.jackson.databind.SerializationFeature;
024import com.fasterxml.jackson.databind.introspect.Annotated;
025import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
026import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
027import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
028import org.apache.wicket.protocol.http.AbstractRequestLogger;
029import org.apache.wicket.protocol.http.RequestLogger;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * JsonRequestLogger uses Jackson to log requests in JSON-format. You will need jackson-mapper in
035 * your classpath, ie. like:
036 * 
037 * <pre>
038 * {@literal
039 * <dependency>
040 *     <groupId>com.fasterxml.jackson.core</groupId>
041 *     <artifactId>jackson-databind</artifactId>
042 *     <version>2.7.1</version>
043 * </dependency>
044 * }
045 * </pre>
046 * 
047 * @author Emond Papegaaij
048 */
049public class JsonRequestLogger extends AbstractRequestLogger
050{
051        // Reusing the logger from RequestLogger
052        private static final Logger LOG = LoggerFactory.getLogger(RequestLogger.class);
053
054        /**
055         * Specify that the 'default' filter should be used for serialization. This filter will prevent
056         * jackson from serializing the request handlers.
057         */
058        private static final class FilteredIntrospector extends JacksonAnnotationIntrospector
059        {
060                @Override
061                public Object findFilterId(Annotated a)
062                {
063                        return "default";
064                }
065        }
066
067        /**
068         * A simple tuple for request and session.
069         */
070        private static final class RequestSessionTuple implements Serializable
071        {
072                private static final long serialVersionUID = 1L;
073
074                private final RequestData request;
075                private final SessionData session;
076
077                public RequestSessionTuple(RequestData request, SessionData session)
078                {
079                        this.request = request;
080                        this.session = session;
081                }
082
083                public RequestData getRequest()
084                {
085                        return request;
086                }
087
088                public SessionData getSession()
089                {
090                        return session;
091                }
092        }
093
094        private final ObjectMapper mapper;
095
096        /**
097         * Construct.
098         */
099        public JsonRequestLogger()
100        {
101                mapper = new ObjectMapper();
102                mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
103                SimpleFilterProvider filters = new SimpleFilterProvider();
104                filters.addFilter("default",
105                        SimpleBeanPropertyFilter.serializeAllExcept("eventTarget", "responseTarget"));
106                mapper.setFilterProvider(filters);
107                mapper.setAnnotationIntrospector(new FilteredIntrospector());
108        }
109
110        /**
111         * @return The mapper used to serialize the log data
112         */
113        protected ObjectMapper getMapper()
114        {
115                return mapper;
116        }
117
118        @Override
119        protected void log(RequestData rd, SessionData sd)
120        {
121                if (LOG.isInfoEnabled())
122                {
123                        LOG.info(getLogString(rd, sd));
124                }
125        }
126
127        protected String getLogString(RequestData rd, SessionData sd)
128        {
129                try
130                {
131                        return getMapper().writeValueAsString(new RequestSessionTuple(rd, sd));
132                }
133                catch (IOException e)
134                {
135                        throw new RuntimeException(e);
136                }
137        }
138}