String spanIndexTemplate(V version) {
    String result = beginTemplate(TYPE_SPAN, version);

    String traceIdMapping = KEYWORD;
    if (!strictTraceId) {
      // Supporting mixed trace ID length is expensive due to needing a special analyzer and
      // "fielddata" which consumes a lot of heap. Sites should only turn off strict trace ID when
      // in a transition, and keep trace ID length transitions as short time as possible.
      traceIdMapping =
        "{ \"type\": \"text\", \"fielddata\": \"true\", \"analyzer\": \"traceId_analyzer\" }";
      result += ("""
        ,
            "analysis": {
              "analyzer": {
                "traceId_analyzer": {
                  "type": "custom",
                  "tokenizer": "keyword",
                  "filter": "traceId_filter"
                }
              },
              "filter": {
                "traceId_filter": {
                  "type": "pattern_capture",
                  "patterns": ["([0-9a-f]{1,16})$"],
                  "preserve_original": true
                }
              }
            }
        """);
    }

    result += "  },\n";

    if (searchEnabled) {
      return result
        + ("  \"mappings\": {\n"
        + maybeWrap(TYPE_SPAN, version, "    \"_source\": {\"excludes\": [\"_q\"] },\n"
        + "    \"dynamic_templates\": [\n"
        + "      {\n"
        + "        \"strings\": {\n"
        + "          \"mapping\": {\n"
        + "            \"type\": \"keyword\",\"norms\": false,"
        + " \"ignore_above\": " + SHORT_STRING_LENGTH + "\n"
        + "          },\n"
        + "          \"match_mapping_type\": \"string\",\n"
        + "          \"match\": \"*\"\n"
        + "        }\n"
        + "      }\n"
        + "    ],\n"
        + "    \"properties\": {\n"
        + "      \"traceId\": " + traceIdMapping + ",\n"
        + "      \"name\": " + KEYWORD + ",\n"
        + "      \"localEndpoint\": {\n"
        + "        \"type\": \"object\",\n"
        + "        \"dynamic\": false,\n"
        + "        \"properties\": { \"serviceName\": " + KEYWORD + " }\n"
        + "      },\n"
        + "      \"remoteEndpoint\": {\n"
        + "        \"type\": \"object\",\n"
        + "        \"dynamic\": false,\n"
        + "        \"properties\": { \"serviceName\": " + KEYWORD + " }\n"
        + "      },\n"
        + "      \"timestamp_millis\": {\n"
        + "        \"type\":   \"date\",\n"
        + "        \"format\": \"epoch_millis\"\n"
        + "      },\n"
        + "      \"duration\": { \"type\": \"long\" },\n"
        + "      \"annotations\": { \"enabled\": false },\n"
        + "      \"tags\": { \"enabled\": false },\n"
        + "      \"_q\": " + KEYWORD + "\n"
        + "    }\n")
        + "  }\n"
        + endTemplate(version));
    }
    return result
      + ("  \"mappings\": {\n"
      + maybeWrap(TYPE_SPAN, version, "    \"properties\": {\n"
      + "      \"traceId\": " + traceIdMapping + ",\n"
      + "      \"annotations\": { \"enabled\": false },\n"
      + "      \"tags\": { \"enabled\": false }\n"
      + "    }\n")
      + "  }\n"
      + endTemplate(version));
  }
Templatized due to version differences. Only fields used in search are declared
zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/VersionSpecificTemplates.java