static void addSearchFields(Span span, JsonGenerator writer) throws IOException {
    long timestampMillis = span.timestampAsLong() / 1000L;
    if (timestampMillis != 0L) writer.writeNumberField("timestamp_millis", timestampMillis);
    if (!span.tags().isEmpty() || !span.annotations().isEmpty()) {
      writer.writeArrayFieldStart("_q");
      for (Annotation a : span.annotations()) {
        if (a.value().length() > SHORT_STRING_LENGTH) continue;
        writer.writeString(a.value());
      }
      for (Map.Entry<String, String> tag : span.tags().entrySet()) {
        int length = tag.getKey().length() + tag.getValue().length() + 1;
        if (length > SHORT_STRING_LENGTH) continue;
        writer.writeString(tag.getKey()); // search is possible by key alone
        writer.writeString(tag.getKey() + "=" + tag.getValue());
      }
      writer.writeEndArray();
    }
  }
In order to allow systems like Kibana to search by timestamp, we add a field "timestamp_millis"
when storing. The cheapest way to do this without changing the codec is prefixing it to the
json. For example. {"traceId":"... becomes {"timestamp_millis":12345,"traceId":"...

<p>Tags are stored as a dictionary. Since some tag names will include inconsistent number of
dots (ex "error" and perhaps "error.message"), we cannot index them naturally with
elasticsearch. Instead, we add an index-only (non-source) field of {@code _q} which includes
valid search queries. For example, the tag {@code error -> 500} results in {@code
"_q":["error", "error=500"]}. This matches the input query syntax, and can be checked manually
with curl.

<p>Ex {@code curl -s localhost:9200/zipkin:span-2017-08-11/_search?q=_q:error=500}

@param searchEnabled encodes timestamp_millis and _q when non-empty
zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/internal/BulkIndexWriter.java