@Test(expected = IllegalArgumentException.class) public void throwsWhenFlagIsOutOfLowerRange() { String traceId = Traceparent.randomHex(16); String spanId = Traceparent.randomHex(8); Traceparent t1 = new Traceparent(0, traceId, spanId, -1); }
/** * This method takes incoming traceparent object and creates a new outbound traceparent object * @param incomingTraceparent * @return */ private static Traceparent processIncomingTraceparent(Traceparent incomingTraceparent, HttpServletRequest request) { Traceparent processedTraceparent = null; // If incoming traceparent is null create a new Traceparent if (incomingTraceparent == null) { // If BackCompt mode is enabled, read the Request-Id Header if (isW3CBackCompatEnabled) { processedTraceparent = processLegacyCorrelation(request); } if (processedTraceparent == null){ processedTraceparent = new Traceparent(); } } else { // create outbound traceparent inheriting traceId, flags from parent. processedTraceparent = new Traceparent(0, incomingTraceparent.getTraceId(), null, incomingTraceparent.getTraceFlags()); } return processedTraceparent; }
/** * Converts traceparent from String to Traceparent object * * @return Traceparent */ public static Traceparent fromString(String s) { if (s == null || s.length() == 0) { return null; } String[] arr = s.split("-"); if (arr.length < 4) { return null; } if (!isHex(arr[0], 2)) { return null; } if (arr[0].equals("00") && arr.length > 4) { return null; } if (!isHex(arr[3], 2)) { return null; } return new Traceparent( (Character.digit(arr[0].charAt(0), 16) << 4) + Character.digit(arr[0].charAt(1), 16), arr[1], arr[2], (Character.digit(arr[3].charAt(0), 16) << 4) + Character.digit(arr[3].charAt(1), 16)); }
@Test public void testTraceparentAreResolved() { //setup Map<String, String> headers = new HashMap<>(); Traceparent t = new Traceparent(); headers.put(TraceContextCorrelation.TRACEPARENT_HEADER_NAME, t.toString()); HttpServletRequest request = ServletUtils.createServletRequestWithHeaders(headers); HttpServletResponse response = (HttpServletResponse)ServletUtils.generateDummyServletResponse(); RequestTelemetry requestTelemetry = new RequestTelemetry(); //run TraceContextCorrelation.resolveCorrelation(request, response, requestTelemetry); //validate we have generated proper ID's Assert.assertNotNull(requestTelemetry.getId()); Assert.assertTrue(requestTelemetry.getId().startsWith(formatedID(t.getTraceId()))); //validate operation context ID's OperationContext operation = requestTelemetry.getContext().getOperation(); Assert.assertEquals(t.getTraceId(), operation.getId()); Assert.assertEquals(formatedID(t.getTraceId() + "." + t.getSpanId()), operation.getParentId()); }
@Test public void canCreateTraceParentFromString() { String traceId = Traceparent.randomHex(16); String spanId = Traceparent.randomHex(8); Traceparent t1 = new Traceparent(0, traceId, spanId, 0); Traceparent t2 = Traceparent.fromString(t1.toString()); Assert.assertEquals(t1.version, t2.version); Assert.assertEquals(t1.traceId, t2.traceId); Assert.assertEquals(t1.spanId, t2.spanId); Assert.assertEquals(t1.traceFlags, t2.traceFlags); // memory reference should be different Assert.assertFalse(t1 == t2); }
/** * Generates child TraceParent by retrieving values from ThreadLocal. * @return Outbound Traceparent */ public static String generateChildDependencyTraceparent() { try { RequestTelemetryContext context = ThreadContext.getRequestTelemetryContext(); //check if context is null, no incoming request is present. // This is likely worker role scenario, where a worker is trying // to create a new outbound call, so generate a new traceparent. if (context == null) { return new Traceparent().toString(); } RequestTelemetry requestTelemetry = context.getHttpRequestTelemetry(); String traceId = requestTelemetry.getContext().getOperation().getId(); Traceparent tp = new Traceparent(0, traceId, null, context.getTraceflag()); // We need to propagate full blown traceparent header. return tp.toString(); } catch (Exception ex) { InternalLogger.INSTANCE.error("Failed to generate child ID. Exception information: %s", ex.toString()); InternalLogger.INSTANCE.trace("Stack trace generated is %s", ExceptionUtils.getStackTrace(ex)); } return null; }
requestTelemetry.setId("|" + processedTraceParent.getTraceId() + "." + processedTraceParent.getSpanId() + "."); requestTelemetry.getContext().getOperation().setId(processedTraceParent.getTraceId()); requestTelemetry.getContext().getOperation().setParentId("|" + processedTraceParent.getTraceId() + "." + incomingTraceparent.getSpanId() + "."); } else { ThreadContext.getRequestTelemetryContext().setTraceflag(processedTraceParent.getTraceFlags());
Traceparent tp = new Traceparent(); headers.put(TraceContextCorrelation.TRACEPARENT_HEADER_NAME, tp.toString()); headers.put(TraceContextCorrelation.TRACESTATE_HEADER_NAME, TraceContextCorrelationTests.getTracestateHeaderValue("id1")); HttpServletRequest request = ServletUtils.createServletRequestWithHeaders(headers); Assert.assertEquals(tp.getTraceId(), exceptionTelemetry.getContext().getOperation().getId()); Assert.assertEquals(requestTelemetry.getId(), exceptionTelemetry.getContext().getOperation().getParentId());
Traceparent tp = new Traceparent(); String incomingId = "|" + tp.getTraceId() + ".bcec871c_1."; headers.put(TelemetryCorrelationUtils.CORRELATION_HEADER_NAME, incomingId); headers.put(TelemetryCorrelationUtils.REQUEST_CONTEXT_HEADER_NAME, TelemetryCorrelationUtilsTests.getRequestContextHeaderValue("id1", null)); Assert.assertFalse(requestTelemetry.getId().startsWith("|"+tp.getTraceId())); Assert.assertNotEquals(tp.getTraceId(), operation.getId()); Assert.assertNotEquals(incomingId, operation.getParentId());
@Test(expected = IllegalArgumentException.class) public void throwsWhenTryingToCreateWithMalformedTraceparentString() { String invalidTraceId = Traceparent.randomHex(32); String invalidSpanId = Traceparent.randomHex(16); String invalidTraceparent = String.format("%02x-%s-%s-%02x", 0, invalidTraceId, invalidSpanId, 0); Traceparent t1 = Traceparent.fromString(invalidTraceparent); }
/** * The constructor that tries to create Traceparent Object from given version, traceId, spanID * and traceFlags. */ public Traceparent(int version, String traceId, String spanId, int traceFlags) { this(version, traceId != null ? traceId : randomHex(16), spanId != null ? spanId : randomHex(8), traceFlags, true); }
/** * This method processes the legacy Request-ID header for backward compatibility. * @param request * @return */ private static Traceparent processLegacyCorrelation(HttpServletRequest request) { String requestId = request.getHeader(TelemetryCorrelationUtils.CORRELATION_HEADER_NAME); try { if (requestId != null && !requestId.isEmpty()) { String legacyOperationId = TelemetryCorrelationUtils.extractRootId(requestId); RequestTelemetry requestTelemetry = ThreadContext.getRequestTelemetryContext().getHttpRequestTelemetry(); requestTelemetry.getContext().getProperties().putIfAbsent("ai_legacyRootID", legacyOperationId); requestTelemetry.getContext().getOperation().setParentId(requestId); return new Traceparent(0, legacyOperationId, null, 0); } } catch (Exception e) { InternalLogger.INSTANCE.error(String.format("unable to create traceparent from legacy request-id header" + " %s", ExceptionUtils.getStackTrace(e))); } return null; }
/** * Helper method to create extract Incoming Traceparent header. This method can return null. * @param request * @return Incoming Traceparent */ private static Traceparent extractIncomingTraceparent(HttpServletRequest request) { Traceparent incomingTraceparent = null; Enumeration<String> traceparents = request.getHeaders(TRACEPARENT_HEADER_NAME); List<String> traceparentList = getEnumerationAsCollection(traceparents); // W3C spec mandates a request should exactly have 1 Traceparent header if (traceparentList.size() != 1) { return null; } try { incomingTraceparent = Traceparent.fromString(traceparentList.get(0)); } catch (Exception e) { InternalLogger.INSTANCE.error(String.format("Received invalid traceparent header with exception %s, " + "distributed trace might be broken", ExceptionUtils.getStackTrace(e))); } return incomingTraceparent; }
private Traceparent(int version, String traceId, String spanId, int traceFlags, boolean check) { if (check) { validate(version, traceId, spanId, traceFlags); } this.version = version; this.traceId = traceId; this.spanId = spanId; this.traceFlags = traceFlags; }
/** * Validates the given input based on W3C specifications. */ private static void validate(int version, String traceId, String spanId, int traceFlags) throws IllegalArgumentException { if (version < 0 || version > 254) { throw new IllegalArgumentException("version must be within range [0, 255)"); } if (!isHex(traceId, 32)) { throw new IllegalArgumentException("invalid traceId"); } if (traceId.equals("00000000000000000000000000000000")) { throw new IllegalArgumentException("invalid traceId"); } if (!isHex(spanId, 16)) { throw new IllegalArgumentException("invalid spanId"); } if (spanId.equals("0000000000000000")) { throw new IllegalArgumentException("invalid spanId"); } if (traceFlags < 0 || traceFlags > 255) { throw new IllegalArgumentException("traceFlags must be within range [0, 255]"); } }
Traceparent tp = new Traceparent(); headers.put(TraceContextCorrelation.TRACEPARENT_HEADER_NAME, tp.toString()); headers.put(TraceContextCorrelation.TRACESTATE_HEADER_NAME, TraceContextCorrelationTests.getTracestateHeaderValue("id1")); HttpServletRequest request = ServletUtils.createServletRequestWithHeaders(headers); Assert.assertNotEquals(tp.getSpanId(), id[1]); Assert.assertTrue(requestTelemetry.getId().startsWith(formatedID(tp.getTraceId()))); Assert.assertEquals(tp.getTraceId(), operation.getId()); Assert.assertEquals(formatedID(tp.getTraceId() + "." + tp.getSpanId()), operation.getParentId());
/** * Generates child TraceParent by retrieving values from ThreadLocal. * @return Outbound Traceparent */ public static String generateChildDependencyTraceparent() { try { RequestTelemetryContext context = ThreadContext.getRequestTelemetryContext(); //check if context is null, no incoming request is present. // This is likely worker role scenario, where a worker is trying // to create a new outbound call, so generate a new traceparent. if (context == null) { return new Traceparent().toString(); } RequestTelemetry requestTelemetry = context.getHttpRequestTelemetry(); String traceId = requestTelemetry.getContext().getOperation().getId(); Traceparent tp = new Traceparent(0, traceId, null, context.getTraceflag()); // We need to propagate full blown traceparent header. return tp.toString(); } catch (Exception ex) { InternalLogger.INSTANCE.error("Failed to generate child ID. Exception information: %s", ex.toString()); InternalLogger.INSTANCE.trace("Stack trace generated is %s", ExceptionUtils.getStackTrace(ex)); } return null; }
requestTelemetry.setId("|" + processedTraceParent.getTraceId() + "." + processedTraceParent.getSpanId() + "."); requestTelemetry.getContext().getOperation().setId(processedTraceParent.getTraceId()); requestTelemetry.getContext().getOperation().setParentId("|" + processedTraceParent.getTraceId() + "." + incomingTraceparent.getSpanId() + "."); } else { ThreadContext.getRequestTelemetryContext().setTraceflag(processedTraceParent.getTraceFlags());
Traceparent tp = new Traceparent(); String incomingId = "|" + tp.getTraceId() + ".bcec871c_1."; headers.put(TelemetryCorrelationUtils.CORRELATION_HEADER_NAME, incomingId); headers.put(TelemetryCorrelationUtils.REQUEST_CONTEXT_HEADER_NAME, TelemetryCorrelationUtilsTests.getRequestContextHeaderValue("id1", null)); Assert.assertNotNull(requestTelemetry.getId()); Assert.assertTrue(requestTelemetry.getId().startsWith("|"+tp.getTraceId())); Assert.assertEquals(tp.getTraceId(), operation.getId()); Assert.assertEquals(incomingId, operation.getParentId());