// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.container.logging; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import com.yahoo.collections.ListMap; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import static java.util.stream.Collectors.toMap; /** *

Information to be logged in the access log.

* *

This class contains the union of all information that can be * logged with all the supported access log formats.

* *

The add methods can be called multiple times, * but the parameters should be different for each * invocation of the same method.

* * This class is thread-safe, but the inner class {@link AdInfo} is not. * * @author tonytv * @author bakksjo */ public class AccessLogEntry { public enum CookieType { b, l, n, geocookie, I, R, Y, M; } // Sadly, there's no way to do compile-time validation of these field references. private static final String[] FIELDS_EXCLUDED_FROM_TOSTRING = new String[] { "monitor" }; private final Object monitor = new Object(); private List adInfos; private String spaceID; private String ipV4AddressInDotDecimalNotation; private long timeStampMillis; private long durationBetweenRequestResponseMillis; private long numBytesReturned; private URI uri; private String remoteAddress; private int remotePort; private String peerAddress; private int peerPort; private CookieType cookieType; private String cookie; private String weekOfRegistration; private String profile; private String internationalInfo; private String contentAttribute; private String webfactsDigitalSignature; private String errorMessage; private String fileName; private String userAgent; private String referer; private String user; private HitCounts hitCounts; private String requestExtra; private String responseExtra; private Boolean resultFromCache; private String httpMethod; private String httpVersion; private String partner; private String adRationale; private String incrementSlotByOneRequest; private String zDataIncrementSlotByOneRequest; private String hostString; private int statusCode; private ListMap keyValues=null; public void setCookie( CookieType type, String cookie) { synchronized (monitor) { requireNull(this.cookieType); requireNull(this.cookie); this.cookieType = type; this.cookie = cookie; } } public CookieType getCookieType() { synchronized (monitor) { return cookieType; } } public String getCookie() { synchronized (monitor) { return cookie; } } public void setWeekOfRegistration( String weekOfRegistration ) { synchronized (monitor) { requireNull(this.weekOfRegistration); this.weekOfRegistration = weekOfRegistration; } } public String getWeekOfRegistration() { synchronized (monitor) { return weekOfRegistration; } } public void setProfile( String profile ) { synchronized (monitor) { requireNull(this.profile); this.profile = profile; } } public String getProfile() { synchronized (monitor) { return profile; } } public void setInternationalInfo( String intl ) { synchronized (monitor) { requireNull(this.internationalInfo); this.internationalInfo = intl; } } public String getInternationalInfo() { synchronized (monitor) { return internationalInfo; } } public void setContentAttribute( String contentAttribute ) { synchronized (monitor) { requireNull(this.contentAttribute); this.contentAttribute = contentAttribute; } } public String getContentAttribute() { synchronized (monitor) { return contentAttribute; } } public void setAdSpaceID(String spaceID) { synchronized (monitor) { requireNull(this.spaceID); this.spaceID = spaceID; } } public String getAdSpaceID() { synchronized (monitor) { return spaceID; } } public void addAdInfo(AdInfo adInfo) { synchronized (monitor) { if (adInfos == null) { adInfos = new ArrayList<>(); } adInfos.add( adInfo ); } } public List getAdInfos() { synchronized (monitor) { if (adInfos == null) { return Collections.emptyList(); } // TODO: The returned list is unmodifiable, but its elements are not. But we're all friendly here, right? return Collections.unmodifiableList(adInfos); } } /** * This class is NOT thread-safe. It is assumed that a single instance is created/written by a single thread, * and all reads happen-after creation/population, i.e. no mutation after the instance is shared between threads. */ public static class AdInfo { private String adServerString; private String adId; private String matchId; private String position; private String property; private String cpc; private String adClientVersion; private String linkId; private String bidPosition; public void setAdID(String id) { this.adId = id; } public String getAdID() { return adId; } public void setMatchID(String id) { this.matchId = id; } public String getMatchID() { return matchId; } public void setPosition(String position) { this.position = position; } public String getPosition() { return position; } public void setProperty(String property) { this.property = property; } public String getProperty() { return property; } public void setCPC(String cpc) { this.cpc = cpc; } public String getCPC() { return cpc; } public void setAdClientVersion(String adClientVersion) { this.adClientVersion = adClientVersion; } public String getAdClientVersion() { return adClientVersion; } public void setLinkID(String id) { this.linkId = id; } public String getLinkID() { return linkId; } public void setBidPosition(String bidPosition) { this.bidPosition = bidPosition; } public String getBidPosition() { return bidPosition; } public AdInfo() {} AdInfo(String adServerString) { this.adServerString = adServerString; } String getAdServerString() { return adServerString; } } public void setWebfactsDigitalSignature(String signature) { synchronized (monitor) { requireNull(this.webfactsDigitalSignature); this.webfactsDigitalSignature = signature; } } public String getWebfactsDigitalSignature() { synchronized (monitor) { return webfactsDigitalSignature; } } public void setErrorMessage(String errorMessage) { synchronized (monitor) { requireNull(this.errorMessage); this.errorMessage = errorMessage; } } public String getErrorMessage() { synchronized (monitor) { return errorMessage; } } public void setFileName(String fileName) { synchronized (monitor) { requireNull(this.fileName); this.fileName = fileName; } } public String getFileName() { synchronized (monitor) { return fileName; } } public void setUserAgent(String userAgent) { synchronized (monitor) { requireNull(this.userAgent); this.userAgent = userAgent; } } public String getUserAgent() { synchronized (monitor) { return userAgent; } } public void setReferer(String referer) { synchronized (monitor) { requireNull(this.referer); this.referer = referer; } } public String getReferer() { synchronized (monitor) { return referer; } } public void setUser(final String user) { synchronized (monitor) { requireNull(this.user); this.user = user; } } public String getUser() { synchronized (monitor) { return user; } } public void setHitCounts(final HitCounts hitCounts) { synchronized (monitor) { requireNull(this.hitCounts); this.hitCounts = hitCounts; } } public HitCounts getHitCounts() { synchronized (monitor) { return hitCounts; } } public String getRequestExtra() { synchronized (monitor) { return requestExtra; } } public String getResponseExtra() { synchronized (monitor) { return responseExtra; } } public void addKeyValue(String key,String value) { synchronized (monitor) { if (keyValues == null) { keyValues = new ListMap<>(); } keyValues.put(key,value); } } public Map> getKeyValues() { synchronized (monitor) { if (keyValues == null) { return null; } final Map> newMapWithImmutableValues = mapValues( keyValues.entrySet(), valueList -> Collections.unmodifiableList(new ArrayList<>(valueList))); return Collections.unmodifiableMap(newMapWithImmutableValues); } } private static Map mapValues( final Set> entrySet, final Function valueConverter) { return entrySet.stream() .collect(toMap( entry -> entry.getKey(), entry -> valueConverter.apply(entry.getValue()))); } public void setResultFromCache(boolean fromCache) { synchronized (monitor) { requireNull(this.resultFromCache); this.resultFromCache = fromCache; } } public Boolean getResultFromCache() { synchronized (monitor) { return resultFromCache; } } public enum HttpMethod { GET, POST; } public void setHttpMethod(HttpMethod method) { setHttpMethod(method.toString()); } public void setHttpMethod(String method) { synchronized (monitor) { requireNull(this.httpMethod); this.httpMethod = method; } } public String getHttpMethod() { synchronized (monitor) { return httpMethod; } } public void setHttpVersion(final String httpVersion) { synchronized (monitor) { requireNull(this.httpVersion); this.httpVersion = httpVersion; } } public String getHttpVersion() { synchronized (monitor) { return httpVersion; } } public void setPartner(String partner) { synchronized (monitor) { requireNull(this.partner); this.partner = partner; } } public String getPartner() { synchronized (monitor) { return partner; } } public void setAdRationale(String adRationale) { synchronized (monitor) { requireNull(this.adRationale); this.adRationale = adRationale; } } public String getAdRationale() { synchronized (monitor) { return adRationale; } } public void setIncrementSlotByOneRequest(String slotName) { synchronized (monitor) { requireNull(this.incrementSlotByOneRequest); this.incrementSlotByOneRequest = slotName; } } public String getIncrementSlotByOneRequest() { synchronized (monitor) { return incrementSlotByOneRequest; } } public void setZDataIncrementSlotByOneRequest(String slotName) { synchronized (monitor) { requireNull(this.zDataIncrementSlotByOneRequest); this.zDataIncrementSlotByOneRequest = slotName; } } public String getZDataIncrementSlotByOneRequest() { synchronized (monitor) { return zDataIncrementSlotByOneRequest; } } public void setHostString(String hostString) { synchronized (monitor) { requireNull(this.hostString); this.hostString = hostString; } } public String getHostString() { synchronized (monitor) { return hostString; } } public void setIpV4Address(String ipV4AddressInDotDecimalNotation) { synchronized (monitor) { requireNull(this.ipV4AddressInDotDecimalNotation); this.ipV4AddressInDotDecimalNotation = ipV4AddressInDotDecimalNotation; } } public String getIpV4Address() { synchronized (monitor) { return ipV4AddressInDotDecimalNotation; } } public void setTimeStamp(long numMillisSince1Jan1970AtMidnightUTC) { synchronized (monitor) { requireZero(this.timeStampMillis); timeStampMillis = numMillisSince1Jan1970AtMidnightUTC; } } public long getTimeStampMillis() { synchronized (monitor) { return timeStampMillis; } } public void setDurationBetweenRequestResponse(long timeInMillis) { synchronized (monitor) { requireZero(this.durationBetweenRequestResponseMillis); durationBetweenRequestResponseMillis = timeInMillis; } } public long getDurationBetweenRequestResponseMillis() { synchronized (monitor) { return durationBetweenRequestResponseMillis; } } public void setReturnedContentSize(int byteCount) { setReturnedContentSize((long) byteCount); } public void setReturnedContentSize(long byteCount) { synchronized (monitor) { requireZero(this.numBytesReturned); numBytesReturned = byteCount; } } public long getReturnedContentSize() { synchronized (monitor) { return numBytesReturned; } } public void setURI(final URI uri) { synchronized (monitor) { requireNull(this.uri); this.uri = uri; } } public URI getURI() { synchronized (monitor) { return uri; } } public void setRemoteAddress(String remoteAddress) { synchronized (monitor) { requireNull(this.remoteAddress); this.remoteAddress = remoteAddress; } } public void setRemoteAddress(final InetSocketAddress remoteAddress) { setRemoteAddress(getIpAddressAsString(remoteAddress)); } private static String getIpAddressAsString(final InetSocketAddress remoteAddress) { final InetAddress inetAddress = remoteAddress.getAddress(); if (inetAddress == null) { return null; } return inetAddress.getHostAddress(); } public String getRemoteAddress() { synchronized (monitor) { return remoteAddress; } } public void setRemotePort(int remotePort) { synchronized (monitor) { requireZero(this.remotePort); this.remotePort = remotePort; } } public int getRemotePort() { synchronized (monitor) { return remotePort; } } public void setPeerAddress(final String peerAddress) { synchronized (monitor) { requireNull(this.peerAddress); this.peerAddress = peerAddress; } } public void setPeerPort(int peerPort) { synchronized (monitor) { requireZero(this.peerPort); this.peerPort = peerPort; } } public int getPeerPort() { synchronized (monitor) { return peerPort; } } public String getPeerAddress() { synchronized (monitor) { return peerAddress; } } public void setStatusCode(int statusCode) { synchronized (monitor) { requireZero(this.statusCode); this.statusCode = statusCode; } } public int getStatusCode() { synchronized (monitor) { return statusCode; } } @Override public String toString() { synchronized (monitor) { return new ReflectionToStringBuilder(this) .setExcludeFieldNames(FIELDS_EXCLUDED_FROM_TOSTRING) .toString(); } } private static void requireNull(final Object value) { if (value != null) { throw new IllegalStateException("Attempt to overwrite field that has been assigned. Value: " + value); } } private static void requireZero(final long value) { if (value != 0) { throw new IllegalStateException("Attempt to overwrite field that has been assigned. Value: " + value); } } private static void requireZero(final int value) { if (value != 0) { throw new IllegalStateException("Attempt to overwrite field that has been assigned. Value: " + value); } } }