// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.container.logging; import com.yahoo.collections.ListMap; import com.yahoo.yolean.trace.TraceNode; import javax.security.auth.x500.X500Principal; import java.net.InetAddress; import java.net.InetSocketAddress; import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; 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. * * @author Tony Vaagenes * @author bakksjo * @author bjorncs */ public class AccessLogEntry { // 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 String ipV4AddressInDotDecimalNotation; private long timeStampMillis; private long durationBetweenRequestResponseMillis; private long numBytesReturned; private String remoteAddress; private int remotePort; private String peerAddress; private int peerPort; private String profile; private String errorMessage; private String fileName; private String userAgent; private String referer; private String user; private HitCounts hitCounts; private String httpMethod; private String httpVersion; private String hostString; private int statusCode; private String scheme; private int localPort; private Principal principal; private X500Principal sslPrincipal; private String rawPath; private String rawQuery; private TraceNode traceNode; private ListMap keyValues=null; public void setProfile( String profile ) { synchronized (monitor) { requireNull(this.profile); this.profile = profile; } } public String getProfile() { synchronized (monitor) { return profile; } } 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 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 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 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 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; } } public String getScheme() { synchronized (monitor) { return scheme; } } public void setScheme(String scheme) { synchronized (monitor) { requireNull(this.scheme); this.scheme = scheme; } } public int getLocalPort() { synchronized (monitor) { return localPort; } } public void setLocalPort(int localPort) { synchronized (monitor) { requireZero(this.localPort); this.localPort = localPort; } } public Principal getUserPrincipal() { synchronized (monitor) { return principal; } } public void setUserPrincipal(Principal principal) { synchronized (monitor) { requireNull(this.principal); this.principal = principal; } } public Principal getSslPrincipal() { synchronized (monitor) { return sslPrincipal; } } public void setSslPrincipal(X500Principal sslPrincipal) { synchronized (monitor) { requireNull(this.sslPrincipal); this.sslPrincipal = sslPrincipal; } } public void setRawPath(String rawPath) { synchronized (monitor) { requireNull(this.rawPath); this.rawPath = rawPath; } } public String getRawPath() { synchronized (monitor) { return rawPath; } } public void setRawQuery(String rawQuery) { synchronized (monitor) { requireNull(this.rawQuery); this.rawQuery = rawQuery; } } public Optional getRawQuery() { synchronized (monitor) { return Optional.ofNullable(rawQuery); } } public void setTrace(TraceNode traceNode) { synchronized (monitor) { requireNull(this.traceNode); this.traceNode = traceNode; } } public TraceNode getTrace() { synchronized (monitor) { return traceNode; } } @Override public String toString() { synchronized (monitor) { return "AccessLogEntry{" + "ipV4AddressInDotDecimalNotation='" + ipV4AddressInDotDecimalNotation + '\'' + ", timeStampMillis=" + timeStampMillis + ", durationBetweenRequestResponseMillis=" + durationBetweenRequestResponseMillis + ", numBytesReturned=" + numBytesReturned + ", remoteAddress='" + remoteAddress + '\'' + ", remotePort=" + remotePort + ", peerAddress='" + peerAddress + '\'' + ", peerPort=" + peerPort + ", profile='" + profile + '\'' + ", errorMessage='" + errorMessage + '\'' + ", fileName='" + fileName + '\'' + ", userAgent='" + userAgent + '\'' + ", referer='" + referer + '\'' + ", user='" + user + '\'' + ", hitCounts=" + hitCounts + ", httpMethod='" + httpMethod + '\'' + ", httpVersion='" + httpVersion + '\'' + ", hostString='" + hostString + '\'' + ", statusCode=" + statusCode + ", scheme='" + scheme + '\'' + ", localPort=" + localPort + ", principal=" + principal + ", sslPrincipal=" + sslPrincipal + ", rawPath='" + rawPath + '\'' + ", rawQuery='" + rawQuery + '\'' + ", trace='" + traceNode + '\'' + ", keyValues=" + keyValues + '}'; } } 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); } } }