You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by wu...@apache.org on 2018/06/05 07:29:46 UTC

[incubator-servicecomb-java-chassis] 04/06: [SCB-616] finish parse logic refactor

This is an automated email from the ASF dual-hosted git repository.

wujimin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-servicecomb-java-chassis.git

commit 34cf795db6383e125f2802bd555dc3b6d42fef1d
Author: yaohaishi <ya...@huawei.com>
AuthorDate: Sun May 27 22:34:13 2018 +0800

    [SCB-616] finish parse logic refactor
---
 .../rest/vertx/accesslog/AccessLogGenerator.java   |   9 +-
 .../accesslog/element/impl/ResponseSizeItem.java   |   4 +
 .../parser/impl/DefaultAccessLogItemCreator.java   | 137 ++++++++++++
 .../impl/VertxRestAccessLogPatternParser.java      | 232 +++++++++++++++++++--
 .../impl/VertxRestAccessLogPatternParserTest.java  | 136 ++++++++++++
 5 files changed, 505 insertions(+), 13 deletions(-)

diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/AccessLogGenerator.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/AccessLogGenerator.java
index f05e8d4..a2079eb 100644
--- a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/AccessLogGenerator.java
+++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/AccessLogGenerator.java
@@ -17,7 +17,11 @@
 
 package org.apache.servicecomb.transport.rest.vertx.accesslog;
 
+import java.util.List;
+
 import org.apache.servicecomb.transport.rest.vertx.accesslog.element.AccessLogItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.parser.AccessLogPatternParser;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.parser.impl.VertxRestAccessLogPatternParser;
 
 import io.vertx.ext.web.RoutingContext;
 
@@ -32,9 +36,12 @@ public class AccessLogGenerator {
    */
   private AccessLogItem<RoutingContext>[] accessLogItems;
 
+  private AccessLogPatternParser<RoutingContext> accessLogPatternParser = new VertxRestAccessLogPatternParser();
+
   @SuppressWarnings("unchecked")
   public AccessLogGenerator(String rawPattern) {
-    accessLogItems = new AccessLogItem[0];
+    List<AccessLogItem<RoutingContext>> accessLogItemList = accessLogPatternParser.parsePattern(rawPattern);
+    accessLogItems = accessLogItemList.toArray(new AccessLogItem[0]);
   }
 
   public String generateLog(AccessLogParam<RoutingContext> accessLogParam) {
diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/element/impl/ResponseSizeItem.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/element/impl/ResponseSizeItem.java
index 0d58226..cb43bd5 100644
--- a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/element/impl/ResponseSizeItem.java
+++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/element/impl/ResponseSizeItem.java
@@ -41,4 +41,8 @@ public class ResponseSizeItem implements AccessLogItem<RoutingContext> {
     long bytesWritten = response.bytesWritten();
     return 0 == bytesWritten ? zeroBytes : String.valueOf(bytesWritten);
   }
+
+  public String getZeroBytes() {
+    return zeroBytes;
+  }
 }
diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/DefaultAccessLogItemCreator.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/DefaultAccessLogItemCreator.java
new file mode 100644
index 0000000..6a2a6fd
--- /dev/null
+++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/DefaultAccessLogItemCreator.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.servicecomb.transport.rest.vertx.accesslog.parser.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.AccessLogItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.CookieItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.DatetimeConfigurableItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.DurationMillisecondItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.DurationSecondItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.FirstLineOfRequestItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.HttpMethodItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.HttpStatusItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.InvocationContextItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.LocalHostItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.LocalPortItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.QueryStringItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.RemoteHostItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.RequestHeaderItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.RequestProtocolItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.ResponseHeaderItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.ResponseSizeItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.TraceIdItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.UrlPathItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.UrlPathWithQueryItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.parser.AccessLogItemMeta;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.parser.VertxRestAccessLogItemCreator;
+
+import io.vertx.ext.web.RoutingContext;
+
+public class DefaultAccessLogItemCreator implements VertxRestAccessLogItemCreator {
+  private static final Map<String, AccessLogItem<RoutingContext>> SIMPLE_ACCESS_LOG_ITEM_MAP = new HashMap<>();
+
+  private static final List<AccessLogItemMeta> SUPPORTED_META_LIST = new ArrayList<>();
+
+  static {
+    AccessLogItem<RoutingContext> item = new HttpMethodItem();
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%m", item);
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("cs-method", item);
+    item = new HttpStatusItem();
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%s", item);
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("sc-status", item);
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%T", new DurationSecondItem());
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%D", new DurationMillisecondItem());
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%h", new RemoteHostItem());
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%v", new LocalHostItem());
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%p", new LocalPortItem());
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%B", new ResponseSizeItem("0"));
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%b", new ResponseSizeItem("-"));
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%r", new FirstLineOfRequestItem());
+    item = new UrlPathItem();
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%U", item);
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("cs-uri-stem", item);
+    item = new QueryStringItem();
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%q", item);
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("cs-uri-query", item);
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("cs-uri", new UrlPathWithQueryItem());
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%H", new RequestProtocolItem());
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%t", new DatetimeConfigurableItem());
+    SIMPLE_ACCESS_LOG_ITEM_MAP.put("%SCB-traceId", new TraceIdItem());
+
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%m", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("cs-method", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%s", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("sc-status", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%T", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%D", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%h", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%v", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%p", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%B", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%b", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%r", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%U", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("cs-uri-stem", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%q", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("cs-uri-query", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("cs-uri", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%H", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%t", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%SCB-traceId", null));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%{", "}t"));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%{", "}i"));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%{", "}o"));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%{", "}C"));
+    SUPPORTED_META_LIST.add(new AccessLogItemMeta("%{", "}SCB-ctx"));
+  }
+
+  @Override
+  public AccessLogItem<RoutingContext> createItem(AccessLogItemMeta accessLogItemMeta, String config) {
+    if (null == accessLogItemMeta.getSuffix()) {
+      // For the simple AccessLogItem
+      return SIMPLE_ACCESS_LOG_ITEM_MAP.get(accessLogItemMeta.getPrefix());
+    }
+
+    // For the configurable AccessLogItem
+    switch (accessLogItemMeta.getSuffix()) {
+      case "}t":
+        return new DatetimeConfigurableItem(config);
+      case "}i":
+        return new RequestHeaderItem(config);
+      case "}o":
+        return new ResponseHeaderItem(config);
+      case "}C":
+        return new CookieItem(config);
+      case "}SCB-ctx":
+        return new InvocationContextItem(config);
+      default:
+        // unexpected situation
+        return null;
+    }
+  }
+
+  @Override
+  public List<AccessLogItemMeta> getAccessLogItemMeta() {
+    return SUPPORTED_META_LIST;
+  }
+}
diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/VertxRestAccessLogPatternParser.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/VertxRestAccessLogPatternParser.java
index 5295f10..f299426 100644
--- a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/VertxRestAccessLogPatternParser.java
+++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/VertxRestAccessLogPatternParser.java
@@ -19,9 +19,11 @@ package org.apache.servicecomb.transport.rest.vertx.accesslog.parser.impl;
 
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.servicecomb.transport.rest.vertx.accesslog.element.AccessLogItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.PlainTextItem;
 import org.apache.servicecomb.transport.rest.vertx.accesslog.parser.AccessLogItemMeta;
 import org.apache.servicecomb.transport.rest.vertx.accesslog.parser.AccessLogPatternParser;
 import org.apache.servicecomb.transport.rest.vertx.accesslog.parser.VertxRestAccessLogItemCreator;
@@ -59,6 +61,7 @@ public class VertxRestAccessLogPatternParser implements AccessLogPatternParser<R
   private List<AccessLogItemMetaWrapper> accessLogItemMetaWrappers = new ArrayList<>();
 
   public VertxRestAccessLogPatternParser() {
+    creators.add(new DefaultAccessLogItemCreator());
     for (VertxRestAccessLogItemCreator creator : creators) {
       for (AccessLogItemMeta accessLogItemMeta : creator.getAccessLogItemMeta()) {
         accessLogItemMetaWrappers.add(new AccessLogItemMetaWrapper(accessLogItemMeta, creator));
@@ -89,17 +92,6 @@ public class VertxRestAccessLogPatternParser implements AccessLogPatternParser<R
   }
 
   /**
-   * @param rawPattern The access log pattern string specified by users.
-   * @return A list of {@linkplain AccessLogItem} which actually generate the content of access log.
-   */
-  @Override
-  public List<AccessLogItem<RoutingContext>> parsePattern(String rawPattern) {
-    List<AccessLogItem<RoutingContext>> itemList = new ArrayList<>();
-    // the algorithm is unimplemented.
-    return itemList;
-  }
-
-  /**
    * Sort all of the {@link AccessLogItemMetaWrapper}, the wrapper that is in front of the others has higher priority.
    * <p/>
    * Sort rule(priority decreased):
@@ -133,10 +125,217 @@ public class VertxRestAccessLogPatternParser implements AccessLogPatternParser<R
    * <li>(%b,)</li>
    * </ol>
    */
-  private void sortAccessLogItemMetaWrapper(List<AccessLogItemMetaWrapper> accessLogItemMetaWrapperList) {
+  public static void sortAccessLogItemMetaWrapper(List<AccessLogItemMetaWrapper> accessLogItemMetaWrapperList) {
     accessLogItemMetaWrapperList.sort(accessLogItemMetaWrapperComparator);
   }
 
+  /**
+   * @param rawPattern The access log pattern string specified by users.
+   * @return A list of {@linkplain AccessLogItem} which actually generate the content of access log.
+   */
+  @Override
+  public List<AccessLogItem<RoutingContext>> parsePattern(String rawPattern) {
+    List<AccessLogItemLocation> locationList = matchAccessLogItem(rawPattern);
+    locationList = fillInPlainTextLocation(rawPattern, locationList);
+
+    return convertToItemList(rawPattern, locationList);
+  }
+
+  /**
+   * Use the {@link #accessLogItemMetaWrappers} to match rawPattern.
+   * Return a list of {@link AccessLogItemLocation}.
+   * Plain text is ignored.
+   */
+  private List<AccessLogItemLocation> matchAccessLogItem(String rawPattern) {
+    List<AccessLogItemLocation> locationList = new ArrayList<>();
+    int cursor = 0;
+    while (cursor < rawPattern.length()) {
+      AccessLogItemLocation candidate = null;
+      for (AccessLogItemMetaWrapper wrapper : accessLogItemMetaWrappers) {
+        if (null != candidate && null == wrapper.getSuffix()) {
+          // TODO:
+          // if user define item("%{","}ab") and item("%{_","}abc") and the pattern is "%{_var}ab}abc"
+          // currently the result is item("%{","_var","}ab"), plaintext("}abc")
+          // is this acceptable?
+
+          // We've gotten an AccessLogItem with suffix, so there is no need to match those without suffix,
+          // just break this match loop
+          cursor = candidate.tail;
+          break;
+        }
+        if (rawPattern.startsWith(wrapper.getPrefix(), cursor)) {
+          if (null == wrapper.getSuffix()) {
+            // for simple type AccessLogItem, there is no need to try to match the next item.
+            candidate = new AccessLogItemLocation(cursor, wrapper);
+            cursor = candidate.tail;
+            break;
+          }
+          // for configurable type, like %{...}i, more check is needed
+          // e.g. "%{varName1}o ${varName2}i" should be divided into
+          // ResponseHeaderItem with varName="varName1" and RequestHeaderItem with varName="varName2"
+          // INSTEAD OF RequestHeaderItem with varName="varName1}o ${varName2"
+          int rear = rawPattern.indexOf(wrapper.getSuffix(), cursor);
+          if (rear < 0) {
+            continue;
+          }
+          if (null == candidate || rear < candidate.suffixIndex) {
+            candidate = new AccessLogItemLocation(cursor, rear, wrapper);
+          }
+          // There is a matched item which is in front of this item, so this item is ignored.
+        }
+      }
+
+      if (candidate == null) {
+        ++cursor;
+        continue;
+      }
+      locationList.add(candidate);
+    }
+
+    return locationList;
+  }
+
+  /**
+   * After processing of {@link #matchAccessLogItem(String)}, all of the placeholders of {@link AccessLogItem} have been
+   * picked out. So the rest part of rawPattern should be treated as plain text. Those parts will be located in this
+   * method and wrapped as {@link PlainTextItem}.
+   * @param rawPattern raw pattern string of access log
+   * @param locationList locations picked out by {@link #matchAccessLogItem(String)}
+   * @return all of the locations including {@link PlainTextItem}.
+   */
+  private List<AccessLogItemLocation> fillInPlainTextLocation(String rawPattern,
+      List<AccessLogItemLocation> locationList) {
+    List<AccessLogItemLocation> resultList = new ArrayList<>();
+    if (locationList.isEmpty()) {
+      resultList.add(createTextPlainItemLocation(0, rawPattern.length()));
+      return resultList;
+    }
+
+    Iterator<AccessLogItemLocation> itemLocationIterator = locationList.iterator();
+    AccessLogItemLocation previousItemLocation = itemLocationIterator.next();
+    if (previousItemLocation.prefixIndex > 0) {
+      resultList.add(createTextPlainItemLocation(0, previousItemLocation.prefixIndex));
+    }
+    resultList.add(previousItemLocation);
+
+    while (itemLocationIterator.hasNext()) {
+      AccessLogItemLocation thisItemLocation = itemLocationIterator.next();
+      if (previousItemLocation.tail < thisItemLocation.prefixIndex) {
+        resultList.add(createTextPlainItemLocation(previousItemLocation.tail, thisItemLocation.prefixIndex));
+      }
+      previousItemLocation = thisItemLocation;
+      resultList.add(previousItemLocation);
+    }
+
+    if (previousItemLocation.tail < rawPattern.length()) {
+      resultList.add(createTextPlainItemLocation(
+          previousItemLocation.tail,
+          rawPattern.length()));
+    }
+    return resultList;
+  }
+
+  private AccessLogItemLocation createTextPlainItemLocation(int front, int rear) {
+    return new AccessLogItemLocation(front, rear);
+  }
+
+  private List<AccessLogItem<RoutingContext>> convertToItemList(String rawPattern,
+      List<AccessLogItemLocation> locationList) {
+    List<AccessLogItem<RoutingContext>> itemList = new ArrayList<>();
+
+    for (AccessLogItemLocation accessLogItemLocation : locationList) {
+      AccessLogItemMetaWrapper accessLogItemMetaWrapper = accessLogItemLocation.accessLogItemMetaWrapper;
+      if (null == accessLogItemMetaWrapper) {
+        // a PlainTextItem location
+        itemList.add(new PlainTextItem(rawPattern.substring(
+            accessLogItemLocation.prefixIndex, accessLogItemLocation.tail
+        )));
+        continue;
+      }
+
+      itemList.add(
+          accessLogItemMetaWrapper.getVertxRestAccessLogItemCreator().createItem(
+              accessLogItemMetaWrapper.getAccessLogItemMeta(),
+              getConfigString(rawPattern, accessLogItemLocation))
+      );
+    }
+
+    return itemList;
+  }
+
+  private String getConfigString(String rawPattern, AccessLogItemLocation accessLogItemLocation) {
+    if (null == accessLogItemLocation.getSuffix()) {
+      // simple AccessLogItem
+      return null;
+    }
+
+    return rawPattern.substring(
+        accessLogItemLocation.prefixIndex + accessLogItemLocation.getPrefix().length(),
+        accessLogItemLocation.suffixIndex);
+  }
+
+  private static class AccessLogItemLocation {
+    /**
+     * prefixIndex = rawPattern.indexOf(prefix)
+     */
+    int prefixIndex;
+
+    /**
+     * suffixIndex = rawPattern.indexOf(suffix)
+     */
+    int suffixIndex;
+
+    /**
+     * tail = suffixIndex + suffix.length()
+     */
+    int tail;
+
+    AccessLogItemMetaWrapper accessLogItemMetaWrapper;
+
+    /**
+     * for {@link PlainTextItem} only
+     */
+    AccessLogItemLocation(int prefixIndex, int suffixIndex) {
+      this.prefixIndex = prefixIndex;
+      this.suffixIndex = suffixIndex;
+      this.tail = suffixIndex;
+    }
+
+    /**
+     * for configurable type AccessLogItem
+     */
+    AccessLogItemLocation(int prefixIndex, int suffixIndex, AccessLogItemMetaWrapper accessLogItemMetaWrapper) {
+      this.prefixIndex = prefixIndex;
+      this.suffixIndex = suffixIndex;
+      this.tail = suffixIndex + accessLogItemMetaWrapper.getSuffix().length();
+      this.accessLogItemMetaWrapper = accessLogItemMetaWrapper;
+    }
+
+    /**
+     * for simple type AccessLogItem
+     */
+    AccessLogItemLocation(int prefixIndex, AccessLogItemMetaWrapper accessLogItemMetaWrapper) {
+      this.prefixIndex = prefixIndex;
+      this.suffixIndex = prefixIndex + accessLogItemMetaWrapper.getPrefix().length();
+      this.tail = this.suffixIndex;
+      this.accessLogItemMetaWrapper = accessLogItemMetaWrapper;
+    }
+
+    public String getPrefix() {
+      if (null == accessLogItemMetaWrapper) {
+        return null;
+      }
+      return accessLogItemMetaWrapper.getPrefix();
+    }
+
+    public String getSuffix() {
+      if (null == accessLogItemMetaWrapper) {
+        return null;
+      }
+      return accessLogItemMetaWrapper.getSuffix();
+    }
+  }
+
   public static class AccessLogItemMetaWrapper {
     private AccessLogItemMeta accessLogItemMeta;
 
@@ -155,5 +354,14 @@ public class VertxRestAccessLogPatternParser implements AccessLogPatternParser<R
     public VertxRestAccessLogItemCreator getVertxRestAccessLogItemCreator() {
       return vertxRestAccessLogItemCreator;
     }
+
+    public String getPrefix() {
+      return accessLogItemMeta.getPrefix();
+    }
+
+    public String getSuffix() {
+      return accessLogItemMeta.getSuffix();
+    }
   }
 }
+
diff --git a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/VertxRestAccessLogPatternParserTest.java b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/VertxRestAccessLogPatternParserTest.java
index 2fdcc2d..0cfd2cd 100644
--- a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/VertxRestAccessLogPatternParserTest.java
+++ b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/accesslog/parser/impl/VertxRestAccessLogPatternParserTest.java
@@ -17,15 +17,151 @@
 
 package org.apache.servicecomb.transport.rest.vertx.accesslog.parser.impl;
 
+import static org.junit.Assert.assertEquals;
+
 import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
 import java.util.function.Function;
 
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.AccessLogItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.CookieItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.DatetimeConfigurableItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.DurationMillisecondItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.DurationSecondItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.FirstLineOfRequestItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.HttpMethodItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.HttpStatusItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.InvocationContextItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.LocalHostItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.LocalPortItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.QueryStringItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.RemoteHostItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.RequestHeaderItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.RequestProtocolItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.ResponseHeaderItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.ResponseSizeItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.TraceIdItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.UrlPathItem;
+import org.apache.servicecomb.transport.rest.vertx.accesslog.element.impl.UrlPathWithQueryItem;
 import org.apache.servicecomb.transport.rest.vertx.accesslog.parser.AccessLogItemMeta;
 import org.apache.servicecomb.transport.rest.vertx.accesslog.parser.impl.VertxRestAccessLogPatternParser.AccessLogItemMetaWrapper;
 import org.junit.Assert;
 import org.junit.Test;
 
+import io.vertx.ext.web.RoutingContext;
+
 public class VertxRestAccessLogPatternParserTest {
+  private static final String ROW_PATTERN = "[cs-method] %m %s%T%D%h%v%p%B%b%r%U%q"
+      + "cs-uri-stemcs-uri-querycs-uri%H%t%{yyyy MM dd HH:mm:ss zzz}t"
+      + "%{yyyy MM dd HH:mm:ss|GMT+0|en-US}t"
+      + "%{incoming-header}i"
+      + "%{outgoing-header}o"
+      + "%{cookie}C"
+      + "%SCB-traceId"
+      + "%{ctx}SCB-ctx";
+
+  private static VertxRestAccessLogPatternParser accessLogPatternParser = new VertxRestAccessLogPatternParser();
+
+  @Test
+  public void testParsePatternFullTest() {
+    List<AccessLogItem<RoutingContext>> result = accessLogPatternParser.parsePattern(ROW_PATTERN);
+    assertEquals(28, result.size());
+
+    assertEquals("[", result.get(0).getFormattedItem(null));
+    assertEquals(HttpMethodItem.class, result.get(1).getClass());
+    assertEquals("] ", result.get(2).getFormattedItem(null));
+    assertEquals(HttpMethodItem.class, result.get(3).getClass());
+    assertEquals(" ", result.get(4).getFormattedItem(null));
+    assertEquals(HttpStatusItem.class, result.get(5).getClass());
+    assertEquals(DurationSecondItem.class, result.get(6).getClass());
+    assertEquals(DurationMillisecondItem.class, result.get(7).getClass());
+    assertEquals(RemoteHostItem.class, result.get(8).getClass());
+    assertEquals(LocalHostItem.class, result.get(9).getClass());
+    assertEquals(LocalPortItem.class, result.get(10).getClass());
+    assertEquals(ResponseSizeItem.class, result.get(11).getClass());
+    assertEquals("0", ((ResponseSizeItem) result.get(11)).getZeroBytes());
+    assertEquals(ResponseSizeItem.class, result.get(12).getClass());
+    assertEquals("-", ((ResponseSizeItem) result.get(12)).getZeroBytes());
+    assertEquals(FirstLineOfRequestItem.class, result.get(13).getClass());
+    assertEquals(UrlPathItem.class, result.get(14).getClass());
+    assertEquals(QueryStringItem.class, result.get(15).getClass());
+    assertEquals(UrlPathItem.class, result.get(16).getClass());
+    assertEquals(QueryStringItem.class, result.get(17).getClass());
+    assertEquals(UrlPathWithQueryItem.class, result.get(18).getClass());
+    assertEquals(RequestProtocolItem.class, result.get(19).getClass());
+    assertEquals(DatetimeConfigurableItem.class, result.get(20).getClass());
+    assertEquals(DatetimeConfigurableItem.DEFAULT_DATETIME_PATTERN,
+        ((DatetimeConfigurableItem) result.get(20)).getPattern());
+    assertEquals(DatetimeConfigurableItem.DEFAULT_LOCALE, ((DatetimeConfigurableItem) result.get(20)).getLocale());
+    assertEquals(TimeZone.getDefault(), ((DatetimeConfigurableItem) result.get(20)).getTimezone());
+    assertEquals("yyyy MM dd HH:mm:ss zzz", ((DatetimeConfigurableItem) result.get(21)).getPattern());
+    assertEquals(DatetimeConfigurableItem.DEFAULT_LOCALE, ((DatetimeConfigurableItem) result.get(21)).getLocale());
+    assertEquals(TimeZone.getDefault(), ((DatetimeConfigurableItem) result.get(21)).getTimezone());
+    assertEquals("yyyy MM dd HH:mm:ss", ((DatetimeConfigurableItem) result.get(22)).getPattern());
+    assertEquals(Locale.forLanguageTag("en-US"), ((DatetimeConfigurableItem) result.get(22)).getLocale());
+    assertEquals(TimeZone.getTimeZone("GMT+0"), ((DatetimeConfigurableItem) result.get(22)).getTimezone());
+    assertEquals(RequestHeaderItem.class, result.get(23).getClass());
+    assertEquals("incoming-header", ((RequestHeaderItem) result.get(23)).getVarName());
+    assertEquals(ResponseHeaderItem.class, result.get(24).getClass());
+    assertEquals("outgoing-header", ((ResponseHeaderItem) result.get(24)).getVarName());
+    assertEquals(CookieItem.class, result.get(25).getClass());
+    assertEquals("cookie", ((CookieItem) result.get(25)).getVarName());
+    assertEquals(TraceIdItem.class, result.get(26).getClass());
+    assertEquals(InvocationContextItem.class, result.get(27).getClass());
+    assertEquals("ctx", ((InvocationContextItem) result.get(27)).getVarName());
+  }
+
+  @Test
+  public void testParsePattern() {
+    String pattern = " %m  cs-uri-stem %{response-header}o ";
+    List<AccessLogItem<RoutingContext>> result = accessLogPatternParser.parsePattern(pattern);
+    assertEquals(7, result.size());
+
+    assertEquals(" ", result.get(0).getFormattedItem(null));
+    assertEquals(HttpMethodItem.class, result.get(1).getClass());
+    assertEquals("  ", result.get(2).getFormattedItem(null));
+    assertEquals(UrlPathItem.class, result.get(3).getClass());
+    assertEquals(" ", result.get(4).getFormattedItem(null));
+    assertEquals(ResponseHeaderItem.class, result.get(5).getClass());
+    assertEquals("response-header", ((ResponseHeaderItem) result.get(5)).getVarName());
+    assertEquals(" ", result.get(6).getFormattedItem(null));
+  }
+
+  @Test
+  public void testParsePatternWithNoBlank() {
+    String pattern = "%mcs-uri-stem%{response-header}o";
+    List<AccessLogItem<RoutingContext>> result = accessLogPatternParser.parsePattern(pattern);
+    assertEquals(3, result.size());
+
+    assertEquals(HttpMethodItem.class, result.get(0).getClass());
+    assertEquals(UrlPathItem.class, result.get(1).getClass());
+    assertEquals(ResponseHeaderItem.class, result.get(2).getClass());
+    assertEquals("response-header", ((ResponseHeaderItem) result.get(2)).getVarName());
+  }
+
+  @Test
+  public void testParsePatternComplex() {
+    String pattern = "%m  cs-uri-stem %{response-header}o abc cs-uri-query %s%{request} header}i plain cs-uri";
+    List<AccessLogItem<RoutingContext>> result = accessLogPatternParser.parsePattern(pattern);
+    assertEquals(12, result.size());
+
+    assertEquals(HttpMethodItem.class, result.get(0).getClass());
+    assertEquals("  ", result.get(1).getFormattedItem(null));
+    assertEquals(UrlPathItem.class, result.get(2).getClass());
+    assertEquals(" ", result.get(3).getFormattedItem(null));
+    assertEquals(ResponseHeaderItem.class, result.get(4).getClass());
+    assertEquals("response-header", ((ResponseHeaderItem) result.get(4)).getVarName());
+    assertEquals(" abc ", result.get(5).getFormattedItem(null));
+    assertEquals(QueryStringItem.class, result.get(6).getClass());
+    assertEquals(" ", result.get(7).getFormattedItem(null));
+    assertEquals(HttpStatusItem.class, result.get(8).getClass());
+    assertEquals(RequestHeaderItem.class, result.get(9).getClass());
+    assertEquals("request} header", ((RequestHeaderItem) result.get(9)).getVarName());
+    assertEquals(" plain ", result.get(10).getFormattedItem(null));
+    assertEquals(UrlPathWithQueryItem.class, result.get(11).getClass());
+  }
 
   Comparator<AccessLogItemMetaWrapper> comparator = VertxRestAccessLogPatternParser.accessLogItemMetaWrapperComparator;
 

-- 
To stop receiving notification emails like this one, please contact
wujimin@apache.org.