SpanParser { public static Span fromKeyValueString(String keyValueStr) { try { Map<String, String> spanFieldsMap = new LinkedHashMap<>(); Map<String, String> tagsMap = new LinkedHashMap<>(); List<TimestampedAnnotation> annotationsList = new ArrayList<>(); int i = 0; int strLen = keyValueStr.length(); int extractKeyStartIndex = 0; int extractKeyEndIndex = 0; int extractValueStartIndex = 0; int numPrecedingBackslashes = 0; ParsingState state = ParsingState.EXTRACTING_KEY; while (i < strLen) { char c = keyValueStr.charAt(i); if (state == ParsingState.EXTRACTING_KEY) { if (c == '=') { extractKeyEndIndex = i; state = ParsingState.EXPECT_VALUE_START_CHAR; } } else if (state == ParsingState.EXPECT_VALUE_START_CHAR) { if (c == '\"') { extractValueStartIndex = i + 1; state = ParsingState.EXTRACTING_VALUE; } else { throw new IllegalStateException( "Span parsing error: Expected but did not find quotes '\"' character for value-start at " + "index " + i ); } } else if (state == ParsingState.EXTRACTING_VALUE) { if (isUnescapedQuotes(c, numPrecedingBackslashes)) { String key = keyValueStr.substring(extractKeyStartIndex, extractKeyEndIndex); String value = keyValueStr.substring(extractValueStartIndex, i); value = unescapeJson(value); boolean isTag = key.startsWith(KEY_VALUE_TAG_PREFIX); boolean isAnnotation = false; if (isTag) { String keyNoTagPrefix = key.substring(KEY_VALUE_TAG_PREFIX.length()); key = unescapeTagKeyForKeyValueFormatDeserialization(keyNoTagPrefix); } else { isAnnotation = key.startsWith(KEY_VALUE_TIMESTAMPED_ANNOTATION_PREFIX); if (isAnnotation) { key = key.substring(KEY_VALUE_TIMESTAMPED_ANNOTATION_PREFIX.length()); } } if (isTag) { tagsMap.put(key, value); } else if (isAnnotation) { long timestamp = Long.parseLong(key); annotationsList.add(TimestampedAnnotation.forEpochMicros(timestamp, value)); } else { spanFieldsMap.put(key, value); } state = ParsingState.EXPECT_COMMA_BEFORE_NEW_KEY; } } else if (state == ParsingState.EXPECT_COMMA_BEFORE_NEW_KEY) { if (c == ',') { extractKeyStartIndex = i + 1; state = ParsingState.EXTRACTING_KEY; } else { throw new IllegalStateException( "Span parsing error: Expected but did not find comma ',' character after value-end at " + "index " + i ); } } else { throw new IllegalStateException("Unhandled state: " + state); } if (c == '\\') { numPrecedingBackslashes++; } else { numPrecedingBackslashes = 0; } i++; } if (state != ParsingState.EXPECT_COMMA_BEFORE_NEW_KEY) { throw new IllegalStateException( "Span parsing error: key/value string did not end after finding a value. Instead ended in state: " + state.name() ); } return fromKeyValueMap(spanFieldsMap, tagsMap, annotationsList); } catch (Exception e) { logger.error("Error extracting Span from key/value string. Defaulting to null. bad_span_key_value_string={}", keyValueStr, e); return null; } } protected  SpanParser(); static String convertSpanToJSON(Span span); static Span fromJSON(String json); static String convertSpanToKeyValueFormat(Span span); static Span fromKeyValueString(String keyValueStr); static String escapeJson(String orig); static String unescapeJson(String escaped); static final String TRACE_ID_FIELD; static final String PARENT_SPAN_ID_FIELD; static final String SPAN_ID_FIELD; static final String SPAN_NAME_FIELD; static final String SAMPLEABLE_FIELD; static final String USER_ID_FIELD; static final String SPAN_PURPOSE_FIELD; static final String START_TIME_EPOCH_MICROS_FIELD; static final String DURATION_NANOS_FIELD; static final String TAGS_FIELD; static final String ANNOTATIONS_LIST_FIELD; static final String ANNOTATION_SUBOBJECT_TIMESTAMP_FIELD; static final String ANNOTATION_SUBOBJECT_VALUE_FIELD; static final String KEY_VALUE_TAG_PREFIX; static final String KEY_VALUE_TIMESTAMPED_ANNOTATION_PREFIX; }
@UseDataProvider("badKeyValueScenarioDataProvider") @Test public void fromKeyValueString_should_return_null_for_garbage_input(BadKeyValueScenario scenario) { String garbageInput = scenario.generateBadKeyValueStr( Span.newBuilder("foo", SpanPurpose.CLIENT) .withTag("footag", "bar") .build() ); Span spanFromKeyValStr = SpanParser.fromKeyValueString(garbageInput); assertThat(spanFromKeyValStr).isNull(); }