AccessLogFormats { static List<AccessLogComponent> parseCustom(String formatStr) { requireNonNull(formatStr, "formatStr"); final ImmutableList.Builder<AccessLogComponent> builder = ImmutableList.builder(); final StringBuilder textBuilder = new StringBuilder(); Condition.Builder condBuilder = null; String variable = null; State state = State.TEXT; for (int i = 0; i < formatStr.length();) { final char ch = formatStr.charAt(i); switch (state) { case TEXT: if (ch == '%') { if (textBuilder.length() > 0) { builder.add(ofText(newStringAndReset(textBuilder))); } condBuilder = null; variable = null; state = State.PERCENT; } else { textBuilder.append(ch); } break; case PERCENT: if (Character.isAlphabetic(ch)) { state = State.TOKEN; continue; } if (Character.isDigit(ch)) { condBuilder = Condition.builder(); state = State.CONDITION; continue; } if (ch == '!') { condBuilder = Condition.builder().setSign(false); state = State.CONDITION; } else if (ch == '{') { state = State.VARIABLE; } break; case CONDITION: if (Character.isDigit(ch)) { textBuilder.append(ch); } else { if (textBuilder.length() > 0) { condBuilder.addHttpStatus(newStringAndReset(textBuilder)); } if (Character.isAlphabetic(ch)) { state = State.TOKEN; continue; } if (ch == '{') { state = State.VARIABLE; } else if (ch != ',') { throw new IllegalArgumentException("Unexpected character in condition:" + ch); } } break; case VARIABLE: if (ch != '}') { textBuilder.append(ch); } else { if (textBuilder.length() > 0) { variable = newStringAndReset(textBuilder); } state = State.TOKEN; } break; case TOKEN: builder.add(newAccessLogComponent(ch, variable, condBuilder)); state = State.TEXT; break; } ++i; } if (state != State.TEXT) { throw new IllegalArgumentException("Unexpected access log format: " + formatStr); } if (textBuilder.length() > 0) { builder.add(ofText(newStringAndReset(textBuilder))); } return builder.build(); } private  AccessLogFormats();   }
@Test void parseSuccess() { List<AccessLogComponent> format; AccessLogComponent entry; HttpHeaderComponent headerEntry; CommonComponent commonComponentEntry; assertThat(AccessLogFormats.parseCustom("%h %l")) .usingRecursiveFieldByFieldElementComparator() .containsSequence(AccessLogComponent.ofPredefinedCommon(AccessLogType.REMOTE_HOST), AccessLogComponent.ofText(" "), AccessLogComponent.ofPredefinedCommon(AccessLogType.RFC931)); format = AccessLogFormats.parseCustom("%200,302{Referer}i"); assertThat(format.size()).isOne(); entry = format.get(0); assertThat(entry).isInstanceOf(HttpHeaderComponent.class); headerEntry = (HttpHeaderComponent) entry; assertThat(headerEntry.condition()).isNotNull(); assertThat(headerEntry.condition().apply(ResponseHeaders.of(HttpStatus.OK))).isTrue(); assertThat(headerEntry.condition().apply(ResponseHeaders.of(HttpStatus.BAD_REQUEST))).isFalse(); assertThat(headerEntry.headerName().toString()) .isEqualToIgnoringCase(HttpHeaderNames.REFERER.toString()); format = AccessLogFormats.parseCustom("%!200,302{User-Agent}i"); assertThat(format.size()).isOne(); entry = format.get(0); assertThat(entry).isInstanceOf(HttpHeaderComponent.class); headerEntry = (HttpHeaderComponent) entry; assertThat(headerEntry.condition()).isNotNull(); assertThat(headerEntry.condition().apply(ResponseHeaders.of(HttpStatus.OK))).isFalse(); assertThat(headerEntry.condition().apply(ResponseHeaders.of(HttpStatus.BAD_REQUEST))).isTrue(); assertThat(headerEntry.headerName().toString()) .isEqualToIgnoringCase(HttpHeaderNames.USER_AGENT.toString()); format = AccessLogFormats.parseCustom("%200b"); assertThat(format.size()).isOne(); entry = format.get(0); assertThat(entry).isInstanceOf(CommonComponent.class); commonComponentEntry = (CommonComponent) entry; assertThat(commonComponentEntry.condition()).isNotNull(); assertThat(commonComponentEntry.condition().apply(ResponseHeaders.of(HttpStatus.OK))).isTrue(); assertThat(commonComponentEntry.condition() .apply(ResponseHeaders.of(HttpStatus.BAD_REQUEST))).isFalse(); format = AccessLogFormats.parseCustom("%!200b"); assertThat(format.size()).isOne(); entry = format.get(0); assertThat(entry).isInstanceOf(CommonComponent.class); commonComponentEntry = (CommonComponent) entry; assertThat(commonComponentEntry.condition()).isNotNull(); assertThat(commonComponentEntry.condition().apply(ResponseHeaders.of(HttpStatus.OK))).isFalse(); assertThat(commonComponentEntry.condition().apply(ResponseHeaders.of(HttpStatus.BAD_REQUEST))).isTrue(); assertThat(AccessLogFormats.parseCustom("").isEmpty()).isTrue(); format = AccessLogFormats.parseCustom( "%{com.linecorp.armeria.server.logging.AccessLogFormatsTest$Attr#KEY" + ":com.linecorp.armeria.server.logging.AccessLogFormatsTest$AttributeStringifier}j"); assertThat(format.size()).isOne(); entry = format.get(0); assertThat(entry).isInstanceOf(AttributeComponent.class); final AttributeComponent attrEntry = (AttributeComponent) entry; assertThat(attrEntry.key().toString()) .isEqualTo("com.linecorp.armeria.server.logging.AccessLogFormatsTest$Attr#KEY"); assertThat(AccessLogFormats.parseCustom("%h00,300{abc}")) .usingRecursiveFieldByFieldElementComparator() .containsSequence(AccessLogComponent.ofPredefinedCommon(AccessLogType.REMOTE_HOST), AccessLogComponent.ofText("00,300{abc}")); assertThat(AccessLogFormats.parseCustom("%a %{c}a %A")) .usingRecursiveFieldByFieldElementComparator() .containsSequence(AccessLogComponent.ofPredefinedCommon(AccessLogType.REMOTE_IP_ADDRESS), AccessLogComponent.ofText(" "), AccessLogComponent.ofPredefinedCommon(AccessLogType.REMOTE_IP_ADDRESS, "c"), AccessLogComponent.ofText(" "), AccessLogComponent.ofPredefinedCommon(AccessLogType.LOCAL_IP_ADDRESS)); }
@Test void parseFailure() { assertThatThrownBy(() -> AccessLogFormats.parseCustom("%x")) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> AccessLogFormats.parseCustom("%!{abc}i")) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> AccessLogFormats.parseCustom("%{abci")) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> AccessLogFormats.parseCustom("%{abc}")) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> AccessLogFormats.parseCustom("%200,300{abc}")) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> AccessLogFormats.parseCustom("%200,30x{abc}")) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> AccessLogFormats.parseCustom("%200,300{abc")) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> AccessLogFormats.parseCustom("%x00,300{abc}")) .isInstanceOf(IllegalArgumentException.class); }
@Test void formatMessage() { final HttpRequest req = HttpRequest.of( RequestHeaders.of(HttpMethod.GET, "/armeria/log", HttpHeaderNames.USER_AGENT, "armeria/x.y.z", HttpHeaderNames.REFERER, "http: HttpHeaderNames.COOKIE, "a=1;b=2")); final RequestId id = RequestId.random(); final ServiceRequestContext ctx = ServiceRequestContext.builder(req) .requestStartTime(requestStartTimeNanos, requestStartTimeMicros) .id(id) .build(); ctx.setAttr(Attr.ATTR_KEY, new Attr("line")); final RequestLogBuilder logBuilder = ctx.logBuilder(); logBuilder.endRequest(); ctx.log().ensureRequestComplete(); logBuilder.responseHeaders(ResponseHeaders.of(HttpStatus.OK, HttpHeaderNames.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8)); logBuilder.responseLength(1024); logBuilder.endResponse(); final RequestLog log = ctx.log().ensureComplete(); final String serviceName = log.serviceName(); final String logName = serviceName.substring(serviceName.lastIndexOf('.') + 1); final String localhostAddress = NetUtil.LOCALHOST.getHostAddress(); final String timestamp = defaultDateTimeFormatter.format(ZonedDateTime.ofInstant( Instant.ofEpochMilli(requestStartTimeMillis), defaultZoneId)); String message; List<AccessLogComponent> format; message = AccessLogger.format(AccessLogFormats.COMMON, log); assertThat(message).isEqualTo( localhostAddress + " - - " + timestamp + " \"GET /armeria/log#" + logName + " h2c\" 200 1024"); message = AccessLogger.format(AccessLogFormats.COMBINED, log); assertThat(message).isEqualTo( localhostAddress + " - - " + timestamp + " \"GET /armeria/log#" + logName + " h2c\" 200 1024" + " \"http: format = AccessLogFormats.parseCustom( "%h %l %u %t \"%r\" %s %b \"%200,302{Referer}i\" \"%!200,304{User-Agent}i\"" + " some-text %{Non-Existing-Header}i"); message = AccessLogger.format(format, log); assertThat(message).isEqualTo( localhostAddress + " - - " + timestamp + " \"GET /armeria/log#" + logName + " h2c\" 200 1024" + " \"http: format = AccessLogFormats.parseCustom( "%h %l %u %t \"%r\" %s %b \"%!200,302{Referer}i\" \"%200,304{User-Agent}i\"" + " some-text %{Non-Existing-Header}i"); message = AccessLogger.format(format, log); assertThat(message).isEqualTo( localhostAddress + " - - " + timestamp + " \"GET /armeria/log#" + logName + " h2c\" 200 1024" + " \"-\" \"armeria/x.y.z\" some-text -"); format = AccessLogFormats.parseCustom( "%{com.linecorp.armeria.server.logging.AccessLogFormatsTest$Attr#KEY" + ":com.linecorp.armeria.server.logging.AccessLogFormatsTest$AttributeStringifier}j"); message = AccessLogger.format(format, log); assertThat(message).isEqualTo("(line)"); format = AccessLogFormats.parseCustom( "%{com.linecorp.armeria.server.logging.AccessLogFormatsTest$Attr#KEY}j"); message = AccessLogger.format(format, log); assertThat(message).isEqualTo("LINE"); format = AccessLogFormats.parseCustom("%{content-type}o"); assertThat(AccessLogger.format(format, log)).isEqualTo(MediaType.PLAIN_TEXT_UTF_8.toString()); format = AccessLogFormats.parseCustom("%I"); assertThat(AccessLogger.format(format, log)).isEqualTo(id.text()); format = AccessLogFormats.parseCustom("%{short}I"); assertThat(AccessLogger.format(format, log)).isEqualTo(id.shortText()); }
@Test void logClientAddress() throws Exception { final InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("10.1.0.1"), 0); final ProxiedAddresses proxied = ProxiedAddresses.of( new InetSocketAddress("10.1.0.2", 5000)); final ServiceRequestContext ctx = ServiceRequestContext.builder(HttpRequest.of(HttpMethod.GET, "/")) .remoteAddress(remote) .proxiedAddresses(proxied) .build(); ctx.logBuilder().endRequest(); ctx.logBuilder().endResponse(); final RequestLog log = ctx.log().ensureComplete(); List<AccessLogComponent> format; format = AccessLogFormats.parseCustom("%a"); assertThat(AccessLogger.format(format, log)).isEqualTo("10.1.0.2"); format = AccessLogFormats.parseCustom("%{c}a"); assertThat(AccessLogger.format(format, log)).isEqualTo("10.1.0.1"); }
@Test void requestLogComponent() { final ServiceRequestContext ctx = ServiceRequestContext.builder(HttpRequest.of(HttpMethod.GET, "/armeria/log")) .requestStartTime(requestStartTimeNanos, requestStartTimeMicros) .build(); final RequestLog log = ctx.log().partial(); final RequestLogBuilder logBuilder = ctx.logBuilder(); List<AccessLogComponent> format; final Instant requestStartTime = Instant.ofEpochMilli(requestStartTimeMillis); logBuilder.endRequest(new IllegalArgumentException("detail_message"), requestEndTimeNanos); format = AccessLogFormats.parseCustom("%{requestStartTimeMillis}L " + "%{requestEndTimeMillis}L " + "%{requestDurationMillis}L"); assertThat(AccessLogger.format(format, log)).isEqualTo( String.join(" ", String.valueOf(requestStartTime.toEpochMilli()), String.valueOf(requestStartTime.plus(duration).toEpochMilli()), String.valueOf(duration.toMillis()))); format = AccessLogFormats.parseCustom("\"%{requestCause}L\""); assertThat(AccessLogger.format(format, log)) .isEqualTo('"' + IllegalArgumentException.class.getSimpleName() + ": detail_message\""); format = AccessLogFormats.parseCustom("%{responseStartTimeMillis}L " + "%{responseEndTimeMillis}L " + "%{responseDurationMillis}L"); assertThat(AccessLogger.format(format, log)).isEqualTo("- - -"); final Duration latency = Duration.ofSeconds(3); final Instant responseStartTime = requestStartTime.plus(latency); final long responseStartTimeNanos = requestStartTimeNanos + latency.toNanos(); logBuilder.startResponse(responseStartTimeNanos, responseStartTime.toEpochMilli() * 1000); logBuilder.endResponse(new IllegalArgumentException(), responseStartTimeNanos + duration.toNanos()); assertThat(AccessLogger.format(format, log)).isEqualTo( String.join(" ", String.valueOf(responseStartTime.toEpochMilli()), String.valueOf(responseStartTime.plus(duration).toEpochMilli()), String.valueOf(duration.toMillis()))); format = AccessLogFormats.parseCustom("\"%{responseCause}L\""); assertThat(AccessLogger.format(format, log)) .isEqualTo('"' + IllegalArgumentException.class.getSimpleName() + '"'); }
@Test void requestLogWithEmptyCause() { final ServiceRequestContext ctx = ServiceRequestContext.of(HttpRequest.of(HttpMethod.GET, "/")); final RequestLogBuilder logBuilder = ctx.logBuilder(); final List<AccessLogComponent> format = AccessLogFormats.parseCustom("%{requestCause}L %{responseCause}L"); logBuilder.endRequest(); logBuilder.endResponse(); assertThat(AccessLogger.format(format, ctx.log().ensureComplete())).isEqualTo("- -"); }
@Test void timestamp() { final ServiceRequestContext ctx = ServiceRequestContext.builder(HttpRequest.of(HttpMethod.GET, "/")) .requestStartTime(requestStartTimeNanos, requestStartTimeMicros) .build(); final RequestLog log = ctx.log().partial(); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%t"), log)) .isEqualTo(formatString(defaultDateTimeFormatter, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{BASIC_ISO_DATE}t"), log)) .isEqualTo(formatString(BASIC_ISO_DATE, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_LOCAL_DATE}t"), log)) .isEqualTo(formatString(ISO_LOCAL_DATE, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_OFFSET_DATE}t"), log)) .isEqualTo(formatString(ISO_OFFSET_DATE, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_DATE}t"), log)) .isEqualTo(formatString(ISO_DATE, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_LOCAL_TIME}t"), log)) .isEqualTo(formatString(ISO_LOCAL_TIME, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_OFFSET_TIME}t"), log)) .isEqualTo(formatString(ISO_OFFSET_TIME, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_TIME}t"), log)) .isEqualTo(formatString(ISO_TIME, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_LOCAL_DATE_TIME}t"), log)) .isEqualTo(formatString(ISO_LOCAL_DATE_TIME, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_OFFSET_DATE_TIME}t"), log)) .isEqualTo(formatString(ISO_OFFSET_DATE_TIME, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_ZONED_DATE_TIME}t"), log)) .isEqualTo(formatString(ISO_ZONED_DATE_TIME, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_DATE_TIME}t"), log)) .isEqualTo(formatString(ISO_DATE_TIME, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_ORDINAL_DATE}t"), log)) .isEqualTo(formatString(ISO_ORDINAL_DATE, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_WEEK_DATE}t"), log)) .isEqualTo(formatString(ISO_WEEK_DATE, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{ISO_INSTANT}t"), log)) .isEqualTo(formatString(ISO_INSTANT, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{RFC_1123_DATE_TIME}t"), log)) .isEqualTo(formatString(RFC_1123_DATE_TIME, requestStartTimeMillis)); assertThat(AccessLogger.format(AccessLogFormats.parseCustom("%{yyyy MM dd}t"), log)) .isEqualTo(formatString(DateTimeFormatter.ofPattern("yyyy MM dd"), requestStartTimeMillis)); }