diff --git a/java/client/src/org/openqa/selenium/remote/RemoteTags.java b/java/client/src/org/openqa/selenium/remote/RemoteTags.java index 855319e729a7d..8a21fb3847f02 100644 --- a/java/client/src/org/openqa/selenium/remote/RemoteTags.java +++ b/java/client/src/org/openqa/selenium/remote/RemoteTags.java @@ -18,8 +18,12 @@ package org.openqa.selenium.remote; import org.openqa.selenium.Capabilities; +import org.openqa.selenium.remote.tracing.AttributeKey; +import org.openqa.selenium.remote.tracing.EventAttribute; +import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.Span; +import java.util.Map; import java.util.function.BiConsumer; public class RemoteTags { @@ -28,12 +32,23 @@ private RemoteTags() { // Utility class } - public static final BiConsumer CAPABILITIES = (span, caps) -> { - span.setAttribute("session.capabilities", String.valueOf(caps)); - }; + public static final BiConsumer + CAPABILITIES = + (span, caps) -> span + .setAttribute(AttributeKey.SESSION_CAPABILITIES.getKey(), String.valueOf(caps)); - public static final BiConsumer SESSION_ID = (span, id) -> { - span.setAttribute("session.id", String.valueOf(id)); - }; + public static final BiConsumer SESSION_ID = (span, id) -> + span.setAttribute(AttributeKey.SESSION_ID.getKey(), String.valueOf(id)); + + public static final BiConsumer, Capabilities> + CAPABILITIES_EVENT = + (map, caps) -> + map.put(AttributeKey.SESSION_CAPABILITIES.getKey(), + EventAttribute.setValue(String.valueOf(caps))); + + public static final BiConsumer, SessionId> + SESSION_ID_EVENT = + (map, id) -> + map.put(AttributeKey.SESSION_ID.getKey(), EventAttribute.setValue(String.valueOf(id))); } diff --git a/java/client/src/org/openqa/selenium/remote/tracing/AttributeKey.java b/java/client/src/org/openqa/selenium/remote/tracing/AttributeKey.java new file mode 100644 index 0000000000000..7d9e358347425 --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/tracing/AttributeKey.java @@ -0,0 +1,62 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC 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.openqa.selenium.remote.tracing; + +public enum AttributeKey { + + EXCEPTION_EVENT("exception"), + EXCEPTION_TYPE("exception.type"), + EXCEPTION_MESSAGE("exception.message"), + EXCEPTION_STACKTRACE("exception.stacktrace"), + + SPAN_KIND("span.kind"), + + HTTP_METHOD("http.method"), + HTTP_URL("http.url"), + HTTP_STATUS_CODE("http.status_code"), + HTTP_TARGET_HOST("http.target_host"), + HTTP_CLIENT_CLASS("http.client_class"), + HTTP_HANDLER_CLASS("http.handler_class"), + + LOGGER_CLASS("logger"), + + DRIVER_RESPONSE("driver.response"), + DRIVER_URL("driver.url"), + DOWNSTREAM_DIALECT("downstream.dialect"), + UPSTREAM_DIALECT("upstream.dialect"), + + SESSION_ID("session.id"), + SESSION_CAPABILITIES("session.capabilities"), + SESSION_URI("session.uri"), + + DATABASE_STATEMENT ("db.statement"), + DATABASE_OPERATION ("db.operation"), + DATABASE_USER ("db.user"), + DATABASE_CONNECTION_STRING ("db.connection_string"), + DATABASE_SYSTEM("db.system"); + + private final String key; + + AttributeKey(String key) { + this.key = key; + } + + public String getKey() { + return this.key; + } +} \ No newline at end of file diff --git a/java/client/src/org/openqa/selenium/remote/tracing/EventAttribute.java b/java/client/src/org/openqa/selenium/remote/tracing/EventAttribute.java index 3d965f2070348..f50d245c88d9a 100644 --- a/java/client/src/org/openqa/selenium/remote/tracing/EventAttribute.java +++ b/java/client/src/org/openqa/selenium/remote/tracing/EventAttribute.java @@ -34,6 +34,22 @@ public static EventAttributeValue setValue(long value) { return new EventAttributeValue(value); } + public static EventAttributeValue setValue(String... value) { + return new EventAttributeValue(value); + } + + public static EventAttributeValue setValue(Long... value) { + return new EventAttributeValue(value); + } + + public static EventAttributeValue setValue(Double... value) { + return new EventAttributeValue(value); + } + + public static EventAttributeValue setValue(Boolean... value) { + return new EventAttributeValue(value); + } + public static EventAttributeValue setValue(double value) { return new EventAttributeValue(value); } diff --git a/java/client/src/org/openqa/selenium/remote/tracing/EventAttributeValue.java b/java/client/src/org/openqa/selenium/remote/tracing/EventAttributeValue.java index c1ab557e4f1a5..ca031a7fb592b 100644 --- a/java/client/src/org/openqa/selenium/remote/tracing/EventAttributeValue.java +++ b/java/client/src/org/openqa/selenium/remote/tracing/EventAttributeValue.java @@ -23,6 +23,10 @@ public class EventAttributeValue { private String stringValue; private Number numberValue; private Boolean booleanValue; + private String[] stringArrayValue; + private Long[] longArrayValue; + private Double[] doubleArrayValue; + private Boolean[] booleanArrayValue; public EventAttributeValue(String value) { this.stringValue = value; @@ -44,6 +48,26 @@ public EventAttributeValue(boolean value) { this.type = Type.BOOLEAN; } + public EventAttributeValue(String[] value) { + this.stringArrayValue = value; + this.type = Type.STRING_ARRAY; + } + + public EventAttributeValue(Long[] value) { + this.longArrayValue = value; + this.type = Type.LONG_ARRAY; + } + + public EventAttributeValue(Double[] value) { + this.doubleArrayValue = value; + this.type = Type.DOUBLE_ARRAY; + } + + public EventAttributeValue(Boolean[] value) { + this.booleanArrayValue = value; + this.type = Type.BOOLEAN_ARRAY; + } + public String getStringValue() { return stringValue; } @@ -56,15 +80,27 @@ public Boolean getBooleanValue() { return booleanValue; } + public String[] getStringArrayValue() { return stringArrayValue; } + + public Long[] getLongArrayValue() { return longArrayValue; } + + public Double[] getDoubleArrayValue() { return doubleArrayValue; } + + public Boolean[] getBooleanArrayValue() { return booleanArrayValue; } + public Type getAttributeType() { return type; } public enum Type { - STRING, BOOLEAN, + BOOLEAN_ARRAY, + DOUBLE, + DOUBLE_ARRAY, LONG, - DOUBLE + LONG_ARRAY, + STRING, + STRING_ARRAY } } diff --git a/java/client/src/org/openqa/selenium/remote/tracing/SpanWrappedHttpHandler.java b/java/client/src/org/openqa/selenium/remote/tracing/SpanWrappedHttpHandler.java index 550885cd46904..bb7caf94a80bc 100644 --- a/java/client/src/org/openqa/selenium/remote/tracing/SpanWrappedHttpHandler.java +++ b/java/client/src/org/openqa/selenium/remote/tracing/SpanWrappedHttpHandler.java @@ -31,6 +31,9 @@ import java.util.logging.Logger; import static org.openqa.selenium.remote.tracing.HttpTracing.newSpanAsChildOf; +import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST_EVENT; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE_EVENT; import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST; import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE; import static org.openqa.selenium.remote.tracing.Tags.KIND; @@ -52,6 +55,10 @@ public SpanWrappedHttpHandler(Tracer tracer, Function namer public HttpResponse execute(HttpRequest req) throws UncheckedIOException { // If there is already a span attached to this request, then do nothing. Object possibleSpan = req.getAttribute("selenium.tracing.span"); + Map attributeMap = new HashMap<>(); + attributeMap.put(AttributeKey.HTTP_HANDLER_CLASS.getKey(), + EventAttribute.setValue(delegate.getClass().getName())); + if (possibleSpan instanceof Span) { return delegate.execute(req); } @@ -72,19 +79,26 @@ public HttpResponse execute(HttpRequest req) throws UncheckedIOException { KIND.accept(span, Span.Kind.SERVER); HTTP_REQUEST.accept(span, req); + HTTP_REQUEST_EVENT.accept(attributeMap, req); + HttpTracing.inject(tracer, span, req); HttpResponse res = delegate.execute(req); HTTP_RESPONSE.accept(span, res); + HTTP_RESPONSE_EVENT.accept(attributeMap, res); + span.addEvent("HTTP request execution complete", attributeMap); return res; } catch (Throwable t) { span.setAttribute("error", true); span.setStatus(Status.UNKNOWN); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error message", EventAttribute.setValue(t.getMessage())); - span.addEvent("Error while executing server request", attributeValueMap); + + EXCEPTION.accept(attributeMap, t); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to execute request: " + t.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + LOG.log(Level.WARNING, "Unable to execute request: " + t.getMessage(), t); throw t; } finally { diff --git a/java/client/src/org/openqa/selenium/remote/tracing/Tags.java b/java/client/src/org/openqa/selenium/remote/tracing/Tags.java index 181ed46af1a03..8dc43eed3047f 100644 --- a/java/client/src/org/openqa/selenium/remote/tracing/Tags.java +++ b/java/client/src/org/openqa/selenium/remote/tracing/Tags.java @@ -20,12 +20,13 @@ import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Map; import java.util.function.BiConsumer; public class Tags { - - private final static Map STATUS_CODE_TO_TRACING_STATUS = Map.of( + private static final Map STATUS_CODE_TO_TRACING_STATUS = Map.of( 401, Status.UNAUTHENTICATED, 404, Status.NOT_FOUND, 408, Status.DEADLINE_EXCEEDED, @@ -40,20 +41,19 @@ private Tags() { } public static final BiConsumer KIND = - (span, kind) -> span.setAttribute("span.kind", kind.toString()); + (span, kind) -> span.setAttribute(AttributeKey.SPAN_KIND.getKey(), kind.toString()); public static final BiConsumer HTTP_REQUEST = (span, req) -> { - span.setAttribute("http.method", req.getMethod().toString()); - span.setAttribute("http.url", req.getUri()); + span.setAttribute(AttributeKey.HTTP_METHOD.getKey(), req.getMethod().toString()); + span.setAttribute(AttributeKey.HTTP_URL.getKey(), req.getUri()); }; public static final BiConsumer HTTP_RESPONSE = (span, res) -> { int statusCode = res.getStatus(); if (res.getTargetHost() != null) { - span.setAttribute("http.target_host", res.getTargetHost()); + span.setAttribute(AttributeKey.HTTP_TARGET_HOST.getKey(), res.getTargetHost()); } - res.getTargetHost(); - span.setAttribute("http.status_code", statusCode); + span.setAttribute(AttributeKey.HTTP_STATUS_CODE.getKey(), statusCode); if (statusCode > 99 && statusCode < 400) { span.setStatus(Status.OK); @@ -65,4 +65,37 @@ private Tags() { span.setStatus(Status.UNKNOWN); } }; + + public static final BiConsumer, HttpRequest> + HTTP_REQUEST_EVENT = + (map, req) -> { + map.put(AttributeKey.HTTP_METHOD.getKey(), + EventAttribute.setValue(req.getMethod().toString())); + map.put(AttributeKey.HTTP_URL.getKey(), EventAttribute.setValue(req.getUri())); + }; + + public static final BiConsumer, HttpResponse> + HTTP_RESPONSE_EVENT = + (map, res) -> { + int statusCode = res.getStatus(); + if (res.getTargetHost() != null) { + map.put(AttributeKey.HTTP_TARGET_HOST.getKey(), + EventAttribute.setValue(res.getTargetHost())); + } + map.put(AttributeKey.HTTP_STATUS_CODE.getKey(), EventAttribute.setValue(statusCode)); + }; + + public static final BiConsumer, Throwable> + EXCEPTION = + (map, t) -> { + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + + map.put(AttributeKey.EXCEPTION_TYPE.getKey(), + EventAttribute.setValue(t.getClass().getName())); + map.put(AttributeKey.EXCEPTION_STACKTRACE.getKey(), + EventAttribute.setValue(sw.toString())); + + }; + } diff --git a/java/client/src/org/openqa/selenium/remote/tracing/TracedHttpClient.java b/java/client/src/org/openqa/selenium/remote/tracing/TracedHttpClient.java index 31b769805f200..c2c0f48d1b548 100644 --- a/java/client/src/org/openqa/selenium/remote/tracing/TracedHttpClient.java +++ b/java/client/src/org/openqa/selenium/remote/tracing/TracedHttpClient.java @@ -18,8 +18,10 @@ package org.openqa.selenium.remote.tracing; import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST_EVENT; import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE; import static org.openqa.selenium.remote.tracing.HttpTracing.newSpanAsChildOf; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE_EVENT; import static org.openqa.selenium.remote.tracing.Tags.KIND; import org.openqa.selenium.internal.Require; @@ -30,6 +32,8 @@ import org.openqa.selenium.remote.http.WebSocket; import java.net.URL; +import java.util.HashMap; +import java.util.Map; public class TracedHttpClient implements HttpClient { @@ -49,11 +53,18 @@ public WebSocket openSocket(HttpRequest request, WebSocket.Listener listener) { @Override public HttpResponse execute(HttpRequest req) { try (Span span = newSpanAsChildOf(tracer, req, "httpclient.execute")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(AttributeKey.HTTP_CLIENT_CLASS.getKey(), + EventAttribute.setValue(delegate.getClass().getName())); + KIND.accept(span, Span.Kind.CLIENT); HTTP_REQUEST.accept(span, req); + HTTP_REQUEST_EVENT.accept(attributeMap, req); tracer.getPropagator().inject(span, req, (r, key, value) -> r.setHeader(key, value)); HttpResponse response = delegate.execute(req); HTTP_RESPONSE.accept(span, response); + HTTP_RESPONSE_EVENT.accept(attributeMap, response); + span.addEvent("HTTP request received response", attributeMap); return response; } } diff --git a/java/client/src/org/openqa/selenium/remote/tracing/opentelemetry/OpenTelemetrySpan.java b/java/client/src/org/openqa/selenium/remote/tracing/opentelemetry/OpenTelemetrySpan.java index c209f8dabc686..eb2c74103bf3f 100644 --- a/java/client/src/org/openqa/selenium/remote/tracing/opentelemetry/OpenTelemetrySpan.java +++ b/java/client/src/org/openqa/selenium/remote/tracing/opentelemetry/OpenTelemetrySpan.java @@ -100,20 +100,37 @@ public Span addEvent(String name, Map attributeMap) otAttributes.setAttribute(key, AttributeValue.booleanAttributeValue(value.getBooleanValue())); break; + case BOOLEAN_ARRAY: + otAttributes.setAttribute(key, AttributeValue.arrayAttributeValue(value.getBooleanArrayValue())); + break; + case DOUBLE: otAttributes.setAttribute(key, AttributeValue.doubleAttributeValue(value.getNumberValue().doubleValue())); break; + case DOUBLE_ARRAY: + otAttributes.setAttribute(key, AttributeValue.arrayAttributeValue(value.getDoubleArrayValue())); + break; + case LONG: otAttributes.setAttribute(key, AttributeValue.longAttributeValue(value.getNumberValue().longValue())); break; + case LONG_ARRAY: + otAttributes.setAttribute(key, AttributeValue.arrayAttributeValue(value.getLongArrayValue())); + break; + case STRING: otAttributes.setAttribute(key, AttributeValue.stringAttributeValue(value.getStringValue())); break; + case STRING_ARRAY: + otAttributes.setAttribute(key, AttributeValue.arrayAttributeValue(value.getStringArrayValue())); + break; + default: - throw new IllegalArgumentException("Unrecognized status value type: " + value.getAttributeType()); + throw new IllegalArgumentException( + "Unrecognized event attribute value type: " + value.getAttributeType()); } } ); diff --git a/java/client/test/org/openqa/selenium/remote/tracing/opentelemetry/TracerTest.java b/java/client/test/org/openqa/selenium/remote/tracing/opentelemetry/TracerTest.java index 0c6ce523b0779..83b165a409090 100644 --- a/java/client/test/org/openqa/selenium/remote/tracing/opentelemetry/TracerTest.java +++ b/java/client/test/org/openqa/selenium/remote/tracing/opentelemetry/TracerTest.java @@ -83,47 +83,336 @@ public void shouldBeAbleToCreateATracer() { } @Test - public void shouldBeAbleToCreateASpanWithEvents() { + public void shouldBeAbleToCreateASpanWithAEvent() { List allSpans = new ArrayList<>(); Tracer tracer = createTracer(allSpans); - Attributes.Builder atAttributes = new Attributes.Builder(); - atAttributes.setAttribute("testString", AttributeValue.stringAttributeValue("attributeValue")); - atAttributes.setAttribute("testBoolean", AttributeValue.booleanAttributeValue(true)); - atAttributes.setAttribute("testFloat", AttributeValue.doubleAttributeValue(5.5f)); - atAttributes.setAttribute("testDouble", AttributeValue.doubleAttributeValue(5.55555)); - atAttributes.setAttribute("testInt", AttributeValue.longAttributeValue(10)); - atAttributes.setAttribute("testLong", AttributeValue.longAttributeValue(100L)); + String event = "Test event"; try (Span span = tracer.getCurrentContext().createSpan("parent")) { - span.addEvent("Test event start"); + span.addEvent(event); + } + + assertThat(allSpans).hasSize(1); + SpanData spanData = allSpans.get(0); + assertThat(spanData.getEvents()).hasSize(1); + + List timedEvents = spanData.getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName) + .isEqualTo(event); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getTotalAttributeCount) + .isEqualTo(0); + } - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("testString", EventAttribute.setValue("attributeValue")); - attributeValueMap.put("testBoolean", EventAttribute.setValue(true)); - attributeValueMap.put("testFloat", EventAttribute.setValue(5.5f)); - attributeValueMap.put("testDouble", EventAttribute.setValue(5.55555)); - attributeValueMap.put("testInt", EventAttribute.setValue(10)); - attributeValueMap.put("testLong", EventAttribute.setValue(100L)); + @Test + public void shouldBeAbleToCreateASpanWithEvents() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String startEvent = "Test event started"; + String endEvent = "Test event ended"; - span.addEvent("Test event end", attributeValueMap); + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + span.addEvent(startEvent); + span.addEvent(endEvent); } assertThat(allSpans).hasSize(1); - SpanData spanData = allSpans.get(0); assertThat(spanData.getEvents()).hasSize(2); List timedEvents = spanData.getEvents(); assertThat(timedEvents).element(0).extracting(SpanData.Event::getName) - .isEqualTo("Test event start"); - assertThat(timedEvents).element(0).extracting(SpanData.Event::getTotalAttributeCount).isEqualTo(0); - assertThat(timedEvents).element(1).extracting(SpanData.Event::getName).isEqualTo("Test event end"); - assertThat(timedEvents).element(1).extracting(SpanData.Event::getTotalAttributeCount).isEqualTo(6); - - List allKeys = new ArrayList<>(); - timedEvents.get(1).getAttributes().forEach((key, value) -> allKeys.add(key)); - assertThat(allKeys).contains("testString", "testBoolean", "testFloat", "testDouble", "testInt", "testLong"); - assertThat(timedEvents.get(1).getAttributes()).isEqualTo(atAttributes.build()); + .isEqualTo(startEvent); + assertThat(timedEvents).element(1).extracting(SpanData.Event::getName) + .isEqualTo(endEvent); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getTotalAttributeCount) + .isEqualTo(0); + } + + @Test + public void shouldBeAbleToCreateSpansWithEvents() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String httpEvent = "HTTP test event "; + String databaseEvent = "Database test event"; + String httpSpan = "http.test"; + String databaseSpan = "db.test"; + + try (Span span = tracer.getCurrentContext().createSpan(httpSpan)) { + span.addEvent(httpEvent); + } + + try (Span span = tracer.getCurrentContext().createSpan(databaseSpan)) { + span.addEvent(databaseEvent); + } + + assertThat(allSpans).hasSize(2); + SpanData httpSpanData = allSpans.get(0); + assertThat(httpSpanData.getEvents()).hasSize(1); + List httpTimedEvents = httpSpanData.getEvents(); + assertThat(httpTimedEvents).element(0).extracting(SpanData.Event::getName) + .isEqualTo(httpEvent); + + SpanData dbSpanData = allSpans.get(1); + assertThat(dbSpanData.getEvents()).hasSize(1); + List dbTimedEvents = dbSpanData.getEvents(); + assertThat(dbTimedEvents).element(0).extracting(SpanData.Event::getName) + .isEqualTo(databaseEvent); + } + + @Test + public void canCreateASpanEventWithBooleanAttribute() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String event = "Test event"; + String attribute = "testBoolean"; + + Attributes.Builder attributes = new Attributes.Builder(); + attributes.setAttribute(attribute, AttributeValue.booleanAttributeValue(false)); + + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(attribute, EventAttribute.setValue(false)); + span.addEvent(event, attributeMap); + } + + assertThat(allSpans).hasSize(1); + List timedEvents = allSpans.get(0).getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName).isEqualTo(event); + assertThat(timedEvents.get(0).getAttributes()).isEqualTo(attributes.build()); + } + + @Test + public void canCreateASpanEventWithBooleanArrayAttributes() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String event = "Test event"; + String arrayKey = "booleanArray"; + String varArgsKey = "booleanVarArgs"; + Boolean[] booleanArray = new Boolean[]{true, false}; + + Attributes.Builder attributes = new Attributes.Builder(); + attributes.setAttribute(arrayKey, AttributeValue.arrayAttributeValue(booleanArray)); + attributes.setAttribute(varArgsKey, AttributeValue.arrayAttributeValue(true, false, true)); + + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(arrayKey, EventAttribute.setValue(booleanArray)); + attributeMap.put(varArgsKey, EventAttribute.setValue(true, false, true)); + span.addEvent(event, attributeMap); + } + + assertThat(allSpans).hasSize(1); + List timedEvents = allSpans.get(0).getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName).isEqualTo(event); + assertThat(timedEvents.get(0).getAttributes()).isEqualTo(attributes.build()); + } + + @Test + public void canCreateASpanEventWithDoubleAttribute() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String event = "Test event"; + String attribute = "testDouble"; + Double attributeValue = 1.1; + + Attributes.Builder attributes = new Attributes.Builder(); + attributes.setAttribute(attribute, AttributeValue.doubleAttributeValue(attributeValue)); + + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(attribute, EventAttribute.setValue(attributeValue)); + span.addEvent(event, attributeMap); + } + + assertThat(allSpans).hasSize(1); + List timedEvents = allSpans.get(0).getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName).isEqualTo(event); + assertThat(timedEvents.get(0).getAttributes()).isEqualTo(attributes.build()); + } + + @Test + public void canCreateASpanEventWithDoubleArrayAttributes() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String event = "Test event"; + String arrayKey = "doubleArray"; + String varArgsKey = "doubleVarArgs"; + Double[] doubleArray = new Double[]{4.5, 2.5}; + + Attributes.Builder attributes = new Attributes.Builder(); + attributes.setAttribute(arrayKey, AttributeValue.arrayAttributeValue(doubleArray)); + attributes.setAttribute(varArgsKey, AttributeValue.arrayAttributeValue(2.2, 5.3)); + + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(arrayKey, EventAttribute.setValue(doubleArray)); + attributeMap.put(varArgsKey, EventAttribute.setValue(2.2, 5.3)); + span.addEvent(event, attributeMap); + } + + assertThat(allSpans).hasSize(1); + List timedEvents = allSpans.get(0).getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName).isEqualTo(event); + assertThat(timedEvents.get(0).getAttributes()).isEqualTo(attributes.build()); + } + + @Test + public void canCreateASpanEventWithLongAttribute() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String event = "Test event"; + String attribute = "testLong"; + Long attributeValue = 500L; + + Attributes.Builder attributes = new Attributes.Builder(); + attributes.setAttribute(attribute, AttributeValue.longAttributeValue(attributeValue)); + + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(attribute, EventAttribute.setValue(attributeValue)); + span.addEvent(event, attributeMap); + } + + assertThat(allSpans).hasSize(1); + List timedEvents = allSpans.get(0).getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName).isEqualTo(event); + assertThat(timedEvents.get(0).getAttributes()).isEqualTo(attributes.build()); + } + + @Test + public void canCreateASpanEventWithLongArrayAttributes() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String event = "Test event"; + String arrayKey = "longArray"; + String varArgsKey = "longVarArgs"; + Long[] longArray = new Long[]{400L, 200L}; + + Attributes.Builder attributes = new Attributes.Builder(); + attributes.setAttribute(arrayKey, AttributeValue.arrayAttributeValue(longArray)); + attributes.setAttribute(varArgsKey, AttributeValue.arrayAttributeValue(250L, 5L)); + + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(arrayKey, EventAttribute.setValue(longArray)); + attributeMap.put(varArgsKey, EventAttribute.setValue(250L, 5L)); + span.addEvent(event, attributeMap); + } + + assertThat(allSpans).hasSize(1); + List timedEvents = allSpans.get(0).getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName).isEqualTo(event); + assertThat(timedEvents.get(0).getAttributes()).isEqualTo(attributes.build()); + } + + @Test + public void canCreateASpanEventWithStringAttribute() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String event = "Test event"; + String attribute = "testString"; + String attributeValue = "attributeValue"; + + Attributes.Builder attributes = new Attributes.Builder(); + attributes.setAttribute(attribute, AttributeValue.stringAttributeValue(attributeValue)); + + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(attribute, EventAttribute.setValue(attributeValue)); + span.addEvent(event, attributeMap); + } + + assertThat(allSpans).hasSize(1); + List timedEvents = allSpans.get(0).getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName).isEqualTo(event); + assertThat(timedEvents.get(0).getAttributes()).isEqualTo(attributes.build()); + } + + @Test + public void canCreateASpanEventWithStringArrayAttributes() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String event = "Test event"; + String arrayKey = "strArray"; + String varArgsKey = "strVarArgs"; + String[] strArray = new String[]{"hey", "hello"}; + + Attributes.Builder attributes = new Attributes.Builder(); + attributes.setAttribute(arrayKey, AttributeValue.arrayAttributeValue(strArray)); + attributes.setAttribute(varArgsKey, AttributeValue.arrayAttributeValue("hi", "hola")); + + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(arrayKey, EventAttribute.setValue(strArray)); + attributeMap.put(varArgsKey, EventAttribute.setValue("hi", "hola")); + span.addEvent(event, attributeMap); + } + + assertThat(allSpans).hasSize(1); + List timedEvents = allSpans.get(0).getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName).isEqualTo(event); + assertThat(timedEvents.get(0).getAttributes()).isEqualTo(attributes.build()); + } + + @Test + public void canCreateASpanEventWithSameAttributeType() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String event = "Test event"; + String attribute = "testString"; + String attributeValue = "Hey"; + + Attributes.Builder attributes = new Attributes.Builder(); + attributes.setAttribute(attribute, AttributeValue.stringAttributeValue(attributeValue)); + attributes.setAttribute(attribute, AttributeValue.stringAttributeValue(attributeValue)); + + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(attribute, EventAttribute.setValue(attributeValue)); + attributeMap.put(attribute, EventAttribute.setValue(attributeValue)); + span.addEvent(event, attributeMap); + } + + assertThat(allSpans).hasSize(1); + List timedEvents = allSpans.get(0).getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName).isEqualTo(event); + assertThat(timedEvents.get(0).getAttributes()).isEqualTo(attributes.build()); + } + + @Test + public void canCreateASpanEventWithMultipleAttributeTypes() { + List allSpans = new ArrayList<>(); + Tracer tracer = createTracer(allSpans); + String event = "Test event"; + String[] stringArray = new String[]{"Hey", "Hello"}; + Long[] longArray = new Long[]{10L, 5L}; + Double[] doubleArray = new Double[]{4.5, 2.5}; + Boolean[] booleanArray = new Boolean[]{true, false}; + + Attributes.Builder attributes = new Attributes.Builder(); + attributes.setAttribute("testFloat", AttributeValue.doubleAttributeValue(5.5f)); + attributes.setAttribute("testInt", AttributeValue.longAttributeValue(10)); + attributes.setAttribute("testStringArray", AttributeValue.arrayAttributeValue(stringArray)); + attributes.setAttribute("testBooleanArray", AttributeValue.arrayAttributeValue(booleanArray)); + + try (Span span = tracer.getCurrentContext().createSpan("parent")) { + + Map attributeMap = new HashMap<>(); + attributeMap.put("testFloat", EventAttribute.setValue(5.5f)); + attributeMap.put("testInt", EventAttribute.setValue(10)); + attributeMap.put("testStringArray", EventAttribute.setValue(stringArray)); + attributeMap.put("testBooleanArray", EventAttribute.setValue(booleanArray)); + + span.addEvent(event, attributeMap); + } + + assertThat(allSpans).hasSize(1); + SpanData spanData = allSpans.get(0); + assertThat(spanData.getEvents()).hasSize(1); + + List timedEvents = spanData.getEvents(); + assertThat(timedEvents).element(0).extracting(SpanData.Event::getName).isEqualTo(event); + assertThat(timedEvents.get(0).getAttributes()).isEqualTo(attributes.build()); } @Test diff --git a/java/server/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java b/java/server/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java index 51d111d377160..b5e7ccfcb9495 100644 --- a/java/server/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java +++ b/java/server/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java @@ -47,8 +47,10 @@ import org.openqa.selenium.json.Json; import org.openqa.selenium.json.JsonOutput; import org.openqa.selenium.remote.NewSessionPayload; +import org.openqa.selenium.remote.SessionId; import org.openqa.selenium.remote.http.HttpClient; import org.openqa.selenium.remote.http.HttpRequest; +import org.openqa.selenium.remote.tracing.AttributeKey; import org.openqa.selenium.remote.tracing.EventAttribute; import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.Span; @@ -82,9 +84,12 @@ import static org.openqa.selenium.grid.data.NodeDrainComplete.NODE_DRAIN_COMPLETE; import static org.openqa.selenium.grid.data.NodeStatusEvent.NODE_STATUS; import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES; +import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES_EVENT; import static org.openqa.selenium.remote.RemoteTags.SESSION_ID; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID_EVENT; import static org.openqa.selenium.remote.http.Contents.reader; import static org.openqa.selenium.remote.tracing.HttpTracing.newSpanAsChildOf; +import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION; public class LocalDistributor extends Distributor { @@ -143,16 +148,26 @@ public CreateSessionResponse newSession(HttpRequest request) throws SessionNotCreatedException { Span span = newSpanAsChildOf(tracer, request, "distributor.new_session"); + Map attributeMap = new HashMap<>(); try ( Reader reader = reader(request); NewSessionPayload payload = NewSessionPayload.create(reader)) { Objects.requireNonNull(payload, "Requests to process must be set."); + attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(), + EventAttribute.setValue(getClass().getName())); + Iterator iterator = payload.stream().iterator(); + attributeMap.put("request.payload", EventAttribute.setValue(payload.toString())); if (!iterator.hasNext()) { - span.addEvent("No capabilities found"); - throw new SessionNotCreatedException("No capabilities found"); + SessionNotCreatedException exception = new SessionNotCreatedException("No capabilities found"); + EXCEPTION.accept(attributeMap, exception); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to create session. No capabilities found: " + + exception.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + throw exception; } Optional> selected; @@ -177,33 +192,54 @@ public CreateSessionResponse newSession(HttpRequest request) .orElseThrow( () -> { span.setAttribute("error", true); - return new SessionNotCreatedException( - "Unable to find provider for session: " + payload.stream() - .map(Capabilities::toString) - .collect(Collectors.joining(", "))); + SessionNotCreatedException + exception = + new SessionNotCreatedException( + "Unable to find provider for session: " + payload.stream() + .map(Capabilities::toString).collect(Collectors.joining(", "))); + EXCEPTION.accept(attributeMap, exception); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue( + "Unable to find provider for session: " + + exception.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + return exception; }) .get(); sessions.add(sessionResponse.getSession()); - SESSION_ID.accept(span, sessionResponse.getSession().getId()); - CAPABILITIES.accept(span, sessionResponse.getSession().getCapabilities()); - span.setAttribute("session.url", sessionResponse.getSession().getUri().toString()); - + SessionId sessionId = sessionResponse.getSession().getId(); + Capabilities caps = sessionResponse.getSession().getCapabilities(); + String sessionUri = sessionResponse.getSession().getUri().toString(); + SESSION_ID.accept(span, sessionId); + CAPABILITIES.accept(span, caps); + SESSION_ID_EVENT.accept(attributeMap, sessionId); + CAPABILITIES_EVENT.accept(attributeMap, caps); + span.setAttribute(AttributeKey.SESSION_URI.getKey(), sessionUri); + attributeMap.put(AttributeKey.SESSION_URI.getKey(), EventAttribute.setValue(sessionUri)); + + span.addEvent("Session created by the distributor", attributeMap); return sessionResponse; } catch (SessionNotCreatedException e) { span.setAttribute("error", true); span.setStatus(Status.ABORTED); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - span.addEvent("Session not created", attributeValueMap); + + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to create session: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + throw e; } catch (IOException e) { span.setAttribute("error", true); span.setStatus(Status.UNKNOWN); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - span.addEvent("Unknown error in LocalDistributor while creating session", attributeValueMap); + + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unknown error in LocalDistributor while creating session: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + throw new SessionNotCreatedException(e.getMessage(), e); } finally { span.close(); diff --git a/java/server/src/org/openqa/selenium/grid/docker/DockerSessionFactory.java b/java/server/src/org/openqa/selenium/grid/docker/DockerSessionFactory.java index 2b3c74da0e88c..7e3521975f77c 100644 --- a/java/server/src/org/openqa/selenium/grid/docker/DockerSessionFactory.java +++ b/java/server/src/org/openqa/selenium/grid/docker/DockerSessionFactory.java @@ -39,6 +39,7 @@ import org.openqa.selenium.remote.http.HttpClient; import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; +import org.openqa.selenium.remote.tracing.AttributeKey; import org.openqa.selenium.remote.tracing.EventAttribute; import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.Span; @@ -63,6 +64,7 @@ import static org.openqa.selenium.remote.Dialect.W3C; import static org.openqa.selenium.remote.http.Contents.string; import static org.openqa.selenium.remote.http.HttpMethod.GET; +import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION; public class DockerSessionFactory implements SessionFactory { @@ -104,74 +106,87 @@ public Optional apply(CreateSessionRequest sessionRequest) { HttpClient client = clientFactory.createClient(remoteAddress); try (Span span = tracer.getCurrentContext().createSpan("docker_session_factory.apply")) { - LOG.info("Creating container, mapping container port 4444 to " + port); - Container container = docker.create(image(image).map(Port.tcp(4444), Port.tcp(port))); - container.start(); - - LOG.info(String.format("Waiting for server to start (container id: %s)", container.getId())); - try { - waitForServerToStart(client, Duration.ofMinutes(1)); - } catch (TimeoutException e) { - span.setAttribute("error", true); - span.setStatus(Status.CANCELLED); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - attributeValueMap - .put("Container id", EventAttribute.setValue(container.getId().toString())); - span.addEvent("Unable to connect to docker server. Stopping container", attributeValueMap); - - container.stop(Duration.ofMinutes(1)); - container.delete(); - LOG.warning(String.format( - "Unable to connect to docker server (container id: %s)", container.getId())); - return Optional.empty(); - } - LOG.info(String.format("Server is ready (container id: %s)", container.getId())); - - Command command = new Command( - null, - DriverCommand.NEW_SESSION(sessionRequest.getCapabilities())); - ProtocolHandshake.Result result; - Response response; - try { - result = new ProtocolHandshake().createSession(client, command); - response = result.createResponse(); - } catch (IOException | RuntimeException e) { - span.setAttribute("error", true); - span.setStatus(Status.CANCELLED); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - attributeValueMap - .put("Container id", EventAttribute.setValue(container.getId().toString())); - span.addEvent("Unable to create session. Stopping container", attributeValueMap); - - container.stop(Duration.ofMinutes(1)); - container.delete(); - LOG.log(Level.WARNING, "Unable to create session: " + e.getMessage(), e); - return Optional.empty(); - } - - SessionId id = new SessionId(response.getSessionId()); - Capabilities capabilities = new ImmutableCapabilities((Map) response.getValue()); - - Dialect downstream = sessionRequest.getDownstreamDialects().contains(result.getDialect()) ? - result.getDialect() : - W3C; - - LOG.info(String.format( - "Created session: %s - %s (container id: %s)", - id, - capabilities, - container.getId())); - return Optional.of(new DockerSession( - container, - tracer, - client, - id, - remoteAddress, - capabilities, - downstream, - result.getDialect())); + Map attributeMap = new HashMap<>(); + attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(), + EventAttribute.setValue(this.getClass().getName())); + LOG.info("Creating container, mapping container port 4444 to " + port); + Container container = docker.create(image(image).map(Port.tcp(4444), Port.tcp(port))); + container.start(); + + attributeMap.put("docker.image", EventAttribute.setValue(image.toString())); + attributeMap.put("container.port", EventAttribute.setValue(port)); + attributeMap.put("container.id", EventAttribute.setValue(container.getId().toString())); + attributeMap.put("docker.server.url", EventAttribute.setValue(remoteAddress.toString())); + + LOG.info(String.format("Waiting for server to start (container id: %s)", container.getId())); + try { + waitForServerToStart(client, Duration.ofMinutes(1)); + span.addEvent("Container started. Docker server ready.", attributeMap); + } catch (TimeoutException e) { + span.setAttribute("error", true); + span.setStatus(Status.CANCELLED); + + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to connect to docker server. Stopping container: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + + container.stop(Duration.ofMinutes(1)); + container.delete(); + LOG.warning(String.format( + "Unable to connect to docker server (container id: %s)", container.getId())); + return Optional.empty(); + } + LOG.info(String.format("Server is ready (container id: %s)", container.getId())); + + Command command = new Command( + null, + DriverCommand.NEW_SESSION(sessionRequest.getCapabilities())); + ProtocolHandshake.Result result; + Response response; + try { + result = new ProtocolHandshake().createSession(client, command); + response = result.createResponse(); + attributeMap.put(AttributeKey.DRIVER_RESPONSE.getKey(), EventAttribute.setValue(response.toString())); + } catch (IOException | RuntimeException e) { + span.setAttribute("error", true); + span.setStatus(Status.CANCELLED); + + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to create session. Stopping and container: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + + container.stop(Duration.ofMinutes(1)); + container.delete(); + LOG.log(Level.WARNING, "Unable to create session: " + e.getMessage(), e); + return Optional.empty(); + } + + SessionId id = new SessionId(response.getSessionId()); + Capabilities capabilities = new ImmutableCapabilities((Map) response.getValue()); + + Dialect downstream = sessionRequest.getDownstreamDialects().contains(result.getDialect()) ? + result.getDialect() : + W3C; + attributeMap.put(AttributeKey.DOWNSTREAM_DIALECT.getKey(), EventAttribute.setValue(downstream.toString())); + attributeMap.put(AttributeKey.DRIVER_RESPONSE.getKey(), EventAttribute.setValue(response.toString())); + + span.addEvent("Docker driver service created session", attributeMap); + LOG.info(String.format( + "Created session: %s - %s (container id: %s)", + id, + capabilities, + container.getId())); + return Optional.of(new DockerSession( + container, + tracer, + client, + id, + remoteAddress, + capabilities, + downstream, + result.getDialect())); } } diff --git a/java/server/src/org/openqa/selenium/grid/log/LoggingOptions.java b/java/server/src/org/openqa/selenium/grid/log/LoggingOptions.java index f731559a0c06e..06d6a1122f031 100644 --- a/java/server/src/org/openqa/selenium/grid/log/LoggingOptions.java +++ b/java/server/src/org/openqa/selenium/grid/log/LoggingOptions.java @@ -18,15 +18,21 @@ package org.openqa.selenium.grid.log; import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.common.Attributes; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.MultiSpanProcessor; import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.TracerSdkProvider; import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.SpanData.Event; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.trace.Status; + import org.openqa.selenium.grid.config.Config; import org.openqa.selenium.internal.Require; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.json.JsonOutput; import org.openqa.selenium.remote.tracing.Tracer; import org.openqa.selenium.remote.tracing.empty.NullTracer; import org.openqa.selenium.remote.tracing.opentelemetry.OpenTelemetryTracer; @@ -38,10 +44,13 @@ import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.logging.Handler; +import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; @@ -59,6 +68,8 @@ public class LoggingOptions { // adding unit tests for this. private static Tracer tracer; + public static final Json JSON = new Json(); + private final Config config; public LoggingOptions(Config config) { @@ -102,7 +113,66 @@ private Tracer createTracer() { exporters.add(SimpleSpanProcessor.newBuilder(new SpanExporter() { @Override public ResultCode export(Collection spans) { - spans.forEach(span -> LOG.fine(String.valueOf(span))); + + spans.forEach(span -> { + LOG.fine(String.valueOf(span)); + + String traceId = span.getTraceId().toLowerBase16(); + String spanId = span.getSpanId().toLowerBase16(); + Status status = span.getStatus(); + List eventList = span.getEvents(); + eventList.forEach(event -> { + Map map = new HashMap<>(); + map.put("eventTime", event.getEpochNanos()); + map.put("traceId", traceId); + map.put("spanId", spanId); + map.put("spanKind", span.getKind().toString()); + map.put("eventName", event.getName()); + + Attributes attributes = event.getAttributes(); + Map attributeMap = new HashMap<>(); + attributes.forEach((key, value) -> { + Object attributeValue = null; + switch (value.getType()) { + case LONG: + attributeValue = value.getLongValue(); + break; + case DOUBLE: + attributeValue = value.getDoubleValue(); + break; + case STRING: + attributeValue = value.getStringValue(); + break; + case BOOLEAN: + attributeValue = value.getBooleanValue(); + break; + case STRING_ARRAY: + attributeValue = value.getStringArrayValue(); + break; + case LONG_ARRAY: + attributeValue = value.getLongArrayValue(); + break; + case DOUBLE_ARRAY: + attributeValue = value.getDoubleArrayValue(); + break; + case BOOLEAN_ARRAY: + attributeValue = value.getBooleanArrayValue(); + break; + default: + throw new IllegalArgumentException( + "Unrecognized event attribute value type: " + value.getType()); + } + attributeMap.put(key, attributeValue); + }); + map.put("attributes", attributeMap); + String jsonString = getJsonString(map); + if (status.isOk()) { + LOG.log(Level.INFO, jsonString); + } else { + LOG.log(Level.WARNING, jsonString); + } + }); + }); return ResultCode.SUCCESS; } @@ -171,4 +241,13 @@ private OutputStream getOutputStream() { }) .orElse(System.out); } + + private String getJsonString(Map map) { + StringBuilder text = new StringBuilder(); + try (JsonOutput json = JSON.newOutput(text).setPrettyPrint(false)) { + json.write(map); + text.append('\n'); + } + return text.toString(); + } } diff --git a/java/server/src/org/openqa/selenium/grid/node/config/DriverServiceSessionFactory.java b/java/server/src/org/openqa/selenium/grid/node/config/DriverServiceSessionFactory.java index 78c74c2aa6a9e..5586c2333fca5 100644 --- a/java/server/src/org/openqa/selenium/grid/node/config/DriverServiceSessionFactory.java +++ b/java/server/src/org/openqa/selenium/grid/node/config/DriverServiceSessionFactory.java @@ -35,6 +35,7 @@ import org.openqa.selenium.remote.SessionId; import org.openqa.selenium.remote.http.HttpClient; import org.openqa.selenium.remote.service.DriverService; +import org.openqa.selenium.remote.tracing.AttributeKey; import org.openqa.selenium.remote.tracing.EventAttribute; import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.Span; @@ -42,6 +43,7 @@ import org.openqa.selenium.remote.tracing.Tracer; import java.net.URI; +import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -49,6 +51,8 @@ import java.util.function.Predicate; import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES; +import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES_EVENT; +import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION; public class DriverServiceSessionFactory implements SessionFactory { @@ -84,12 +88,19 @@ public Optional apply(CreateSessionRequest sessionRequest) { } try (Span span = tracer.getCurrentContext().createSpan("driver_service_factory.apply")) { - CAPABILITIES.accept(span, sessionRequest.getCapabilities()); + Map attributeMap = new HashMap<>(); + Capabilities capabilities = sessionRequest.getCapabilities(); + CAPABILITIES.accept(span, capabilities); + CAPABILITIES_EVENT.accept(attributeMap, capabilities); + attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(), EventAttribute.setValue(this.getClass().getName())); + DriverService service = builder.build(); try { service.start(); - HttpClient client = clientFactory.createClient(service.getUrl()); + URL serviceURL = service.getUrl(); + attributeMap.put(AttributeKey.DRIVER_URL.getKey(), EventAttribute.setValue(serviceURL.toString())); + HttpClient client = clientFactory.createClient(serviceURL); Command command = new Command( null, @@ -105,6 +116,11 @@ public Optional apply(CreateSessionRequest sessionRequest) { Response response = result.createResponse(); + attributeMap.put(AttributeKey.UPSTREAM_DIALECT.getKey(), EventAttribute.setValue(upstream.toString())); + attributeMap.put(AttributeKey.DOWNSTREAM_DIALECT.getKey(), EventAttribute.setValue(downstream.toString())); + attributeMap.put(AttributeKey.DRIVER_RESPONSE.getKey(), EventAttribute.setValue(response.toString())); + + // TODO: This is a nasty hack. Try and make it elegant. Capabilities caps = new ImmutableCapabilities((Map) response.getValue()); @@ -118,6 +134,7 @@ public Optional apply(CreateSessionRequest sessionRequest) { } } + span.addEvent("Driver service created session", attributeMap); return Optional.of( new ProtocolConvertingSession( tracer, @@ -135,11 +152,10 @@ public void stop() { } catch (Exception e) { span.setAttribute("error", true); span.setStatus(Status.CANCELLED); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - span.addEvent( - "Error while creating session with the driver service. Stopping driver service.", - attributeValueMap); + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Error while creating session with the driver service. Stopping driver service: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); service.stop(); return Optional.empty(); } diff --git a/java/server/src/org/openqa/selenium/grid/node/local/LocalNode.java b/java/server/src/org/openqa/selenium/grid/node/local/LocalNode.java index 5839ecc541966..60f86670eb8f3 100644 --- a/java/server/src/org/openqa/selenium/grid/node/local/LocalNode.java +++ b/java/server/src/org/openqa/selenium/grid/node/local/LocalNode.java @@ -47,6 +47,7 @@ import org.openqa.selenium.remote.SessionId; import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; +import org.openqa.selenium.remote.tracing.AttributeKey; import org.openqa.selenium.remote.tracing.EventAttribute; import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.Span; @@ -77,7 +78,9 @@ import static org.openqa.selenium.grid.node.CapabilityResponseEncoder.getEncoder; import static org.openqa.selenium.remote.HttpSessionId.getSessionId; import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES; +import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES_EVENT; import static org.openqa.selenium.remote.RemoteTags.SESSION_ID; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID_EVENT; import static org.openqa.selenium.remote.http.Contents.asJson; import static org.openqa.selenium.remote.http.Contents.string; import static org.openqa.selenium.remote.http.HttpMethod.DELETE; @@ -175,17 +178,23 @@ public Optional newSession(CreateSessionRequest sessionRe Require.nonNull("Session request", sessionRequest); try (Span span = tracer.getCurrentContext().createSpan("node.new_session")) { + Map attributeMap = new HashMap<>(); + attributeMap + .put(AttributeKey.LOGGER_CLASS.getKey(), EventAttribute.setValue(getClass().getName())); LOG.fine("Creating new session using span: " + span); - span.setAttribute("session_count", getCurrentSessionCount()); + attributeMap.put("session.request.capabilities", + EventAttribute.setValue(sessionRequest.getCapabilities().toString())); + attributeMap.put("session.request.downstreamdialect", + EventAttribute.setValue(sessionRequest.getDownstreamDialects().toString())); + int currentSessionCount = getCurrentSessionCount(); + span.setAttribute("current.session.count", currentSessionCount); + attributeMap.put("current.session.count", EventAttribute.setValue(currentSessionCount)); if (getCurrentSessionCount() >= maxSessionCount) { span.setAttribute("error", true); span.setStatus(Status.RESOURCE_EXHAUSTED); - Map attributeValueMap = new HashMap<>(); - attributeValueMap - .put("Current session count value", EventAttribute.setValue(getCurrentSessionCount())); - attributeValueMap.put("Max session count value", EventAttribute.setValue(maxSessionCount)); - span.addEvent("Max session count reached"); + attributeMap.put("max.session.count", EventAttribute.setValue(maxSessionCount)); + span.addEvent("Max session count reached", attributeMap); return Optional.empty(); } @@ -206,7 +215,7 @@ public Optional newSession(CreateSessionRequest sessionRe if (slotToUse == null) { span.setAttribute("error", true); span.setStatus(Status.NOT_FOUND); - span.addEvent("No slot matched capabilities " + sessionRequest.getCapabilities()); + span.addEvent("No slot matched capabilities ", attributeMap); return Optional.empty(); } @@ -216,18 +225,31 @@ public Optional newSession(CreateSessionRequest sessionRe slotToUse.release(); span.setAttribute("error", true); span.setStatus(Status.NOT_FOUND); - span.addEvent("No slots available for capabilities " + sessionRequest.getCapabilities()); + span.addEvent("No slots available for capabilities ", attributeMap); return Optional.empty(); } ActiveSession session = possibleSession.get(); currentSessions.put(session.getId(), slotToUse); - SESSION_ID.accept(span, session.getId()); - CAPABILITIES.accept(span, session.getCapabilities()); - span.setAttribute("session.downstream.dialect", session.getDownstreamDialect().toString()); - span.setAttribute("session.upstream.dialect", session.getUpstreamDialect().toString()); - span.setAttribute("session.uri", session.getUri().toString()); + SessionId sessionId = session.getId(); + Capabilities caps = session.getCapabilities(); + SESSION_ID.accept(span, sessionId); + CAPABILITIES.accept(span, caps); + SESSION_ID_EVENT.accept(attributeMap, sessionId); + CAPABILITIES_EVENT.accept(attributeMap, caps); + String downstream = session.getDownstreamDialect().toString(); + String upstream = session.getUpstreamDialect().toString(); + String sessionUri = session.getUri().toString(); + span.setAttribute(AttributeKey.DOWNSTREAM_DIALECT.getKey(), downstream); + span.setAttribute(AttributeKey.UPSTREAM_DIALECT.getKey(), upstream); + span.setAttribute(AttributeKey.SESSION_URI.getKey(), sessionUri); + + attributeMap.put(AttributeKey.DOWNSTREAM_DIALECT.getKey(), EventAttribute.setValue(downstream)); + attributeMap.put(AttributeKey.UPSTREAM_DIALECT.getKey(), EventAttribute.setValue(upstream)); + attributeMap.put(AttributeKey.SESSION_URI.getKey(), EventAttribute.setValue(sessionUri)); + + span.addEvent("Session created by node", attributeMap); // The session we return has to look like it came from the node, since we might be dealing // with a webdriver implementation that only accepts connections from localhost diff --git a/java/server/src/org/openqa/selenium/grid/router/GridStatusHandler.java b/java/server/src/org/openqa/selenium/grid/router/GridStatusHandler.java index c45531c4c42dd..52921b751fdde 100644 --- a/java/server/src/org/openqa/selenium/grid/router/GridStatusHandler.java +++ b/java/server/src/org/openqa/selenium/grid/router/GridStatusHandler.java @@ -26,6 +26,7 @@ import org.openqa.selenium.remote.http.HttpHandler; import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; +import org.openqa.selenium.remote.tracing.AttributeKey; import org.openqa.selenium.remote.tracing.EventAttribute; import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.HttpTracing; @@ -53,7 +54,9 @@ import static org.openqa.selenium.remote.http.Contents.string; import static org.openqa.selenium.remote.http.HttpMethod.GET; import static org.openqa.selenium.remote.tracing.HttpTracing.newSpanAsChildOf; +import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION; import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE_EVENT; class GridStatusHandler implements HttpHandler { @@ -93,16 +96,19 @@ public HttpResponse execute(HttpRequest req) { long start = System.currentTimeMillis(); try (Span span = newSpanAsChildOf(tracer, req, "router.status")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(), + EventAttribute.setValue(getClass().getName())); DistributorStatus status; try { status = EXECUTOR_SERVICE.submit(span.wrap(distributor::getStatus)).get(2, SECONDS); } catch (ExecutionException | TimeoutException e) { span.setAttribute("error", true); span.setStatus(Status.CANCELLED); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - span.addEvent("Unable to get distributor status due to execution error or timeout", - attributeValueMap); + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to get distributor status due to execution error or timeout: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); return new HttpResponse().setContent(asJson( ImmutableMap.of("value", ImmutableMap.of( @@ -111,9 +117,9 @@ public HttpResponse execute(HttpRequest req) { } catch (InterruptedException e) { span.setAttribute("error", true); span.setStatus(Status.ABORTED); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - span.addEvent("Interruption while getting distributor status", attributeValueMap); + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Interruption while getting distributor status: " + e.getMessage())); Thread.currentThread().interrupt(); return new HttpResponse().setContent(asJson( @@ -172,26 +178,35 @@ public HttpResponse execute(HttpRequest req) { value.put("nodes", nodeResults.stream() .map(summary -> { - try { - return summary.get(); - } catch (ExecutionException e) { - span.setAttribute("error", true); - span.setStatus(Status.NOT_FOUND); - span.addEvent("Unable to get Node information"); - throw wrap(e); - } catch (InterruptedException e) { - span.setAttribute("error", true); - span.setStatus(Status.NOT_FOUND); - span.addEvent("Unable to get Node information"); - Thread.currentThread().interrupt(); - throw wrap(e); + try { + return summary.get(); + } catch (ExecutionException e) { + span.setAttribute("error", true); + span.setStatus(Status.NOT_FOUND); + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to get Node information: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + throw wrap(e); + } catch (InterruptedException e) { + span.setAttribute("error", true); + span.setStatus(Status.NOT_FOUND); + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to get Node information: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + Thread.currentThread().interrupt(); + throw wrap(e); } }) .collect(toList())); HttpResponse res = new HttpResponse().setContent(asJson(ImmutableMap.of("value", value.build()))); HTTP_RESPONSE.accept(span, res); + HTTP_RESPONSE_EVENT.accept(attributeMap, res); + attributeMap.put("grid.status", EventAttribute.setValue(ready)); span.setStatus(Status.OK); + span.addEvent("Computed grid status", attributeMap); return res; } } diff --git a/java/server/src/org/openqa/selenium/grid/router/HandleSession.java b/java/server/src/org/openqa/selenium/grid/router/HandleSession.java index b283b4660612e..56473b2a14f31 100644 --- a/java/server/src/org/openqa/selenium/grid/router/HandleSession.java +++ b/java/server/src/org/openqa/selenium/grid/router/HandleSession.java @@ -30,6 +30,7 @@ import org.openqa.selenium.remote.http.HttpHandler; import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; +import org.openqa.selenium.remote.tracing.AttributeKey; import org.openqa.selenium.remote.tracing.EventAttribute; import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.HttpTracing; @@ -45,8 +46,12 @@ import static org.openqa.selenium.remote.HttpSessionId.getSessionId; import static org.openqa.selenium.remote.RemoteTags.SESSION_ID; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID_EVENT; +import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION; import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST_EVENT; import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE_EVENT; class HandleSession implements HttpHandler { @@ -71,26 +76,44 @@ class HandleSession implements HttpHandler { @Override public HttpResponse execute(HttpRequest req) { try (Span span = HttpTracing.newSpanAsChildOf(tracer, req, "router.handle_session")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(AttributeKey.HTTP_HANDLER_CLASS.getKey(), + EventAttribute.setValue(getClass().getName())); + HTTP_REQUEST.accept(span, req); + HTTP_REQUEST_EVENT.accept(attributeMap, req); SessionId id = getSessionId(req.getUri()).map(SessionId::new) - .orElseThrow(() -> new NoSuchSessionException("Cannot find session: " + req)); + .orElseThrow(() -> { + NoSuchSessionException exception = new NoSuchSessionException("Cannot find session: " + req); + EXCEPTION.accept(attributeMap, exception); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue( + "Unable to execute request for an existing session: " + exception.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + return exception; + }); SESSION_ID.accept(span, id); + SESSION_ID_EVENT.accept(attributeMap, id); try { HttpTracing.inject(tracer, span, req); HttpResponse res = knownSessions.get(id, loadSessionId(tracer, span, id)).execute(req); HTTP_RESPONSE.accept(span, res); + HTTP_RESPONSE_EVENT.accept(attributeMap, res); + span.addEvent("Session request execution complete", attributeMap); return res; } catch (ExecutionException e) { span.setAttribute("error", true); span.setStatus(Status.CANCELLED); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - span.addEvent("Error in executing request for an existing session", attributeValueMap); + + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to execute request for an existing session: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { diff --git a/java/server/src/org/openqa/selenium/grid/sessionmap/jdbc/JdbcBackedSessionMap.java b/java/server/src/org/openqa/selenium/grid/sessionmap/jdbc/JdbcBackedSessionMap.java index d294f9037d9c5..182410cb2bdca 100644 --- a/java/server/src/org/openqa/selenium/grid/sessionmap/jdbc/JdbcBackedSessionMap.java +++ b/java/server/src/org/openqa/selenium/grid/sessionmap/jdbc/JdbcBackedSessionMap.java @@ -30,12 +30,19 @@ import org.openqa.selenium.internal.Require; import org.openqa.selenium.json.Json; import org.openqa.selenium.remote.SessionId; +import org.openqa.selenium.remote.tracing.AttributeKey; import org.openqa.selenium.remote.tracing.EventAttribute; import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.Span; import org.openqa.selenium.remote.tracing.Status; import org.openqa.selenium.remote.tracing.Tracer; +import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES; +import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES_EVENT; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID; +import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID_EVENT; + import java.io.Closeable; import java.net.URI; import java.net.URISyntaxException; @@ -57,8 +64,12 @@ public class JdbcBackedSessionMap extends SessionMap implements Closeable { private static final String SESSION_ID_COL = "session_ids"; private static final String SESSION_CAPS_COL = "session_caps"; private static final String SESSION_URI_COL = "session_uri"; - private static final String DATABASE_STATEMENT = "db.statement"; - private static final String DATABASE_OPERATION = "db.operation"; + private static final String DATABASE_STATEMENT = AttributeKey.DATABASE_STATEMENT.getKey(); + private static final String DATABASE_OPERATION = AttributeKey.DATABASE_OPERATION.getKey(); + private static final String DATABASE_USER = AttributeKey.DATABASE_USER.getKey(); + private static final String DATABASE_CONNECTION_STRING = AttributeKey.DATABASE_CONNECTION_STRING.getKey(); + private static String jdbcUser; + private static String jdbcUrl; private final EventBus bus; private final Connection connection; @@ -84,6 +95,8 @@ public static SessionMap create(Config config) { Connection connection; try { + jdbcUser = sessionMapOptions.getJdbcUser(); + jdbcUrl = sessionMapOptions.getJdbcUrl(); connection = sessionMapOptions.getJdbcConnection(); } catch (SQLException e) { throw new ConfigException(e); @@ -107,20 +120,34 @@ public boolean add(Session session) { try (Span span = tracer.getCurrentContext().createSpan( "INSERT into sessions_map (session_ids, session_uri, session_caps) values (?, ?, ?) ")) { + Map attributeMap = new HashMap<>(); + SESSION_ID.accept(span, session.getId()); + SESSION_ID_EVENT.accept(attributeMap, session.getId()); + CAPABILITIES.accept(span, session.getCapabilities()); + CAPABILITIES_EVENT.accept(attributeMap, session.getCapabilities()); + setCommonSpanAttributes(span); + setCommonEventAttributes(attributeMap); + attributeMap.put(AttributeKey.SESSION_URI.getKey(), + EventAttribute.setValue(session.getUri().toString())); + try (PreparedStatement statement = insertSessionStatement(session)) { - setCommonSpanAttributes(span); - span.setAttribute(DATABASE_STATEMENT, statement.toString()); + String statementStr = statement.toString(); + span.setAttribute(DATABASE_STATEMENT, statementStr); span.setAttribute(DATABASE_OPERATION, "insert"); + attributeMap.put(DATABASE_STATEMENT, EventAttribute.setValue(statementStr)); + attributeMap.put(DATABASE_OPERATION, EventAttribute.setValue("insert")); - return statement.executeUpdate() >= 1; + int rowCount = statement.executeUpdate(); + attributeMap.put("rows.added", EventAttribute.setValue(rowCount)); + span.addEvent("Inserted into the database", attributeMap); + return rowCount >= 1; } catch (SQLException e) { span.setAttribute("error", true); span.setStatus(Status.CANCELLED); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - attributeValueMap - .put("Session id", EventAttribute.setValue(session.getId().toString())); - span.addEvent("Unable to add session information to the database.", attributeValueMap); + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to add session information to the database: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); throw new JdbcException(e); } @@ -134,24 +161,37 @@ public Session get(SessionId id) throws NoSuchSessionException { URI uri = null; Capabilities caps = null; String rawUri = null; - Map attributeValueMap = new HashMap<>(); - attributeValueMap - .put("Session id", EventAttribute.setValue(id.toString())); + Map attributeMap = new HashMap<>(); try (Span span = tracer.getCurrentContext().createSpan( "SELECT * from sessions_map where session_ids = ?")) { + SESSION_ID.accept(span, id); + SESSION_ID_EVENT.accept(attributeMap, id); + setCommonSpanAttributes(span); + setCommonEventAttributes(attributeMap); + try (PreparedStatement statement = readSessionStatement(id)) { - setCommonSpanAttributes(span); - span.setAttribute(DATABASE_STATEMENT, statement.toString()); + String statementStr = statement.toString(); + span.setAttribute(DATABASE_STATEMENT, statementStr); span.setAttribute(DATABASE_OPERATION, "select"); + attributeMap.put(DATABASE_OPERATION, EventAttribute.setValue("select")); + attributeMap.put(DATABASE_STATEMENT, EventAttribute.setValue(statementStr)); try (ResultSet sessions = statement.executeQuery()) { if (!sessions.next()) { + NoSuchSessionException + exception = + new NoSuchSessionException("Unable to find session."); span.setAttribute("error", true); span.setStatus(Status.NOT_FOUND); - span.addEvent("Session id does not exist in the database.", attributeValueMap); - - throw new NoSuchSessionException("Unable to find..."); + EXCEPTION.accept(attributeMap, exception); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue( + "Session id does not exist in the database :" + exception + .getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); + + throw exception; } rawUri = sessions.getString(SESSION_URI_COL); @@ -160,27 +200,35 @@ public Session get(SessionId id) throws NoSuchSessionException { caps = rawCapabilities == null ? new ImmutableCapabilities() : JSON.toType(rawCapabilities, Capabilities.class); + } + CAPABILITIES_EVENT.accept(attributeMap, caps); try { + attributeMap.put(AttributeKey.SESSION_URI.getKey(), EventAttribute.setValue(rawUri)); uri = new URI(rawUri); } catch (URISyntaxException e) { span.setAttribute("error", true); span.setStatus(Status.INTERNAL); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - span.addEvent("Unable to convert session id to uri.", attributeValueMap); + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.SESSION_URI.getKey(), EventAttribute.setValue(rawUri)); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to convert session id to uri: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); throw new NoSuchSessionException( String.format("Unable to convert session id (%s) to uri: %s", id, rawUri), e); } + span.addEvent("Retrieved session from the database", attributeMap); return new Session(id, uri, caps); } catch (SQLException e) { span.setAttribute("error", true); span.setStatus(Status.CANCELLED); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - span.addEvent("Unable to get session information from the database.", attributeValueMap); - + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to get session information from the database: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); throw new JdbcException(e); } } @@ -191,22 +239,30 @@ public void remove(SessionId id) { Require.nonNull("Session ID", id); try (Span span = tracer.getCurrentContext().createSpan( "DELETE from sessions_map where session_ids = ?")) { + Map attributeMap = new HashMap<>(); + SESSION_ID.accept(span, id); + SESSION_ID_EVENT.accept(attributeMap, id); + setCommonSpanAttributes(span); + setCommonEventAttributes(attributeMap); try (PreparedStatement statement = getDeleteSqlForSession(id)) { - setCommonSpanAttributes(span); - span.setAttribute(DATABASE_STATEMENT, statement.toString()); + String statementStr = statement.toString(); + span.setAttribute(DATABASE_STATEMENT, statementStr); span.setAttribute(DATABASE_OPERATION, "delete"); - - statement.executeUpdate(); + attributeMap.put(DATABASE_STATEMENT, EventAttribute.setValue(statementStr)); + attributeMap.put(DATABASE_OPERATION, EventAttribute.setValue("delete")); + + int rowCount = statement.executeUpdate(); + attributeMap.put("rows.deleted", EventAttribute.setValue(rowCount)); + span.addEvent("Deleted session from the database", attributeMap); + } catch (SQLException e) { span.setAttribute("error", true); span.setStatus(Status.CANCELLED); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - attributeValueMap - .put("Session id", EventAttribute.setValue(id.toString())); - span.addEvent("Unable to delete session information from the database.", attributeValueMap); - + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to delete session information from the database: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); throw new JdbcException(e.getMessage()); } } @@ -261,11 +317,20 @@ private PreparedStatement getDeleteSqlForSession(SessionId sessionId) throws SQL private void setCommonSpanAttributes(Span span) { span.setAttribute("span.kind", Span.Kind.CLIENT.toString()); - if (JdbcSessionMapOptions.jdbcUser != null) { - span.setAttribute("db.user", JdbcSessionMapOptions.jdbcUser); + if (jdbcUser != null) { + span.setAttribute(DATABASE_USER, jdbcUser); + } + if (jdbcUrl != null) { + span.setAttribute(DATABASE_CONNECTION_STRING, jdbcUrl); + } + } + + private void setCommonEventAttributes(Map attributeMap) { + if (jdbcUser != null) { + attributeMap.put(DATABASE_USER, EventAttribute.setValue(jdbcUser)); } - if (JdbcSessionMapOptions.jdbcUrl != null) { - span.setAttribute("db.connection_string", JdbcSessionMapOptions.jdbcUrl); + if (jdbcUrl != null) { + attributeMap.put(DATABASE_CONNECTION_STRING, EventAttribute.setValue(jdbcUrl)); } } } diff --git a/java/server/src/org/openqa/selenium/grid/sessionmap/jdbc/JdbcSessionMapOptions.java b/java/server/src/org/openqa/selenium/grid/sessionmap/jdbc/JdbcSessionMapOptions.java index ae382b3331ad7..81258ddadbaff 100644 --- a/java/server/src/org/openqa/selenium/grid/sessionmap/jdbc/JdbcSessionMapOptions.java +++ b/java/server/src/org/openqa/selenium/grid/sessionmap/jdbc/JdbcSessionMapOptions.java @@ -30,8 +30,10 @@ public class JdbcSessionMapOptions { private static final String SESSIONS_SECTION = "sessions"; private static final Logger LOG = Logger.getLogger(JdbcSessionMapOptions.class.getName()); - protected static String jdbcUrl; - protected static String jdbcUser; + + private String jdbcUrl; + private String jdbcUser; + private String jdbcPassword; private final Config config; @@ -39,24 +41,30 @@ public JdbcSessionMapOptions(Config config) { Require.nonNull("Config", config); this.config = config; - } - - public Connection getJdbcConnection() throws SQLException { try { - - jdbcUrl = config.get(SESSIONS_SECTION, "jdbc-url").get(); - jdbcUser = config.get(SESSIONS_SECTION, "jdbc-user").get(); - - String jdbcPassword = config.get(SESSIONS_SECTION, "jdbc-password").get(); + this.jdbcUrl = config.get(SESSIONS_SECTION, "jdbc-url").get(); + this.jdbcUser = config.get(SESSIONS_SECTION, "jdbc-user").get(); + this.jdbcPassword = config.get(SESSIONS_SECTION, "jdbc-password").get(); if (jdbcUrl.isEmpty()) { throw new JdbcException( "Missing JDBC Url value. Add sessions option value --jdbc-url "); } - return DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); } catch (NoSuchElementException e) { throw new JdbcException( "Missing session options. Check and add all the following options \n --jdbc-url \n --jdbc-user \n --jdbc-password "); } } + + public Connection getJdbcConnection() throws SQLException { + return DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + } + + public String getJdbcUrl() { + return jdbcUrl; + } + + public String getJdbcUser() { + return jdbcUser; + } } diff --git a/java/server/src/org/openqa/selenium/grid/sessionmap/local/LocalSessionMap.java b/java/server/src/org/openqa/selenium/grid/sessionmap/local/LocalSessionMap.java index 26037abea3ae2..4faed4906eb0d 100644 --- a/java/server/src/org/openqa/selenium/grid/sessionmap/local/LocalSessionMap.java +++ b/java/server/src/org/openqa/selenium/grid/sessionmap/local/LocalSessionMap.java @@ -26,9 +26,13 @@ import org.openqa.selenium.grid.sessionmap.SessionMap; import org.openqa.selenium.internal.Require; import org.openqa.selenium.remote.SessionId; +import org.openqa.selenium.remote.tracing.AttributeKey; +import org.openqa.selenium.remote.tracing.EventAttribute; +import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.Span; import org.openqa.selenium.remote.tracing.Tracer; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; @@ -36,6 +40,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import static org.openqa.selenium.grid.data.SessionClosedEvent.SESSION_CLOSED; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID_EVENT; public class LocalSessionMap extends SessionMap { @@ -50,8 +56,14 @@ public LocalSessionMap(Tracer tracer, EventBus bus) { bus.addListener(SESSION_CLOSED, event -> { try (Span span = tracer.getCurrentContext().createSpan("local_sessionmap.remove")) { - SessionId id = event.getData(SessionId.class); - knownSessions.remove(id); + Map attributeMap = new HashMap<>(); + attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(), + EventAttribute.setValue(getClass().getName())); + SessionId id = event.getData(SessionId.class); + SESSION_ID.accept(span, id); + SESSION_ID_EVENT.accept(attributeMap, id); + knownSessions.remove(id); + span.addEvent("Deleted session from local session map", attributeMap); } }); } @@ -76,7 +88,14 @@ public boolean add(Session session) { writeLock.lock(); try (Span span = tracer.getCurrentContext().createSpan("local_sessionmap.add")) { try { + Map attributeMap = new HashMap<>(); + attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(), + EventAttribute.setValue(getClass().getName())); + SessionId id = session.getId(); + SESSION_ID.accept(span, id); + SESSION_ID_EVENT.accept(attributeMap, id); knownSessions.put(session.getId(), session); + span.addEvent("Added session into local session map", attributeMap); } finally { writeLock.unlock(); } diff --git a/java/server/src/org/openqa/selenium/grid/sessionmap/redis/RedisBackedSessionMap.java b/java/server/src/org/openqa/selenium/grid/sessionmap/redis/RedisBackedSessionMap.java index 233ff98c05877..af2c2b5f70595 100644 --- a/java/server/src/org/openqa/selenium/grid/sessionmap/redis/RedisBackedSessionMap.java +++ b/java/server/src/org/openqa/selenium/grid/sessionmap/redis/RedisBackedSessionMap.java @@ -18,6 +18,11 @@ package org.openqa.selenium.grid.sessionmap.redis; import static org.openqa.selenium.grid.data.SessionClosedEvent.SESSION_CLOSED; +import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES; +import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES_EVENT; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID_EVENT; +import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION; import com.google.common.collect.ImmutableMap; import io.lettuce.core.KeyValue; @@ -35,6 +40,7 @@ import org.openqa.selenium.json.Json; import org.openqa.selenium.redis.GridRedisClient; import org.openqa.selenium.remote.SessionId; +import org.openqa.selenium.remote.tracing.AttributeKey; import org.openqa.selenium.remote.tracing.EventAttribute; import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.Span; @@ -52,12 +58,15 @@ public class RedisBackedSessionMap extends SessionMap { private static final Logger LOG = Logger.getLogger(RedisBackedSessionMap.class.getName()); private static final Json JSON = new Json(); - private static final String REDIS_URI_KEY="session.uri_key"; - private static final String REDIS_URI_VALUE="session.uri_value"; - private static final String REDIS_CAPABILITIES_KEY="session.capabilities_key"; - private static final String REDIS_CAPABILITIES_VALUE="session.capabilities_value"; + private static final String REDIS_URI_KEY = "session.uri_key"; + private static final String REDIS_URI_VALUE = "session.uri_value"; + private static final String REDIS_CAPABILITIES_KEY = "session.capabilities_key"; + private static final String REDIS_CAPABILITIES_VALUE = "session.capabilities_value"; + private static final String DATABASE_SYSTEM = AttributeKey.DATABASE_SYSTEM.getKey(); + private static final String DATABASE_OPERATION = AttributeKey.DATABASE_OPERATION.getKey(); private final GridRedisClient connection; private final EventBus bus; + private final URI serverUri; public RedisBackedSessionMap(Tracer tracer, URI serverUri, EventBus bus) { super(tracer); @@ -65,6 +74,7 @@ public RedisBackedSessionMap(Tracer tracer, URI serverUri, EventBus bus) { Require.nonNull("Redis Server Uri", serverUri); this.bus = Require.nonNull("Event bus", bus); this.connection = new GridRedisClient(serverUri); + this.serverUri = serverUri; this.bus.addListener(SESSION_CLOSED, event -> { SessionId id = event.getData(SessionId.class); remove(id); @@ -84,7 +94,13 @@ public boolean add(Session session) { Require.nonNull("Session to add", session); try (Span span = tracer.getCurrentContext().createSpan("MSET sessionUriKey capabilitiesKey ")) { + Map attributeMap = new HashMap<>(); + SESSION_ID.accept(span, session.getId()); + SESSION_ID_EVENT.accept(attributeMap, session.getId()); + CAPABILITIES.accept(span, session.getCapabilities()); + CAPABILITIES_EVENT.accept(attributeMap, session.getCapabilities()); setCommonSpanAttributes(span); + setCommonEventAttributes(attributeMap); String uriKey = uriKey(session.getId()); String uriValue = session.getUri().toString(); @@ -95,7 +111,14 @@ public boolean add(Session session) { span.setAttribute(REDIS_URI_VALUE, uriValue); span.setAttribute(REDIS_CAPABILITIES_KEY, capabilitiesKey); span.setAttribute(REDIS_CAPABILITIES_VALUE, capabilitiesJSON); - + span.setAttribute(DATABASE_OPERATION, "MSET"); + attributeMap.put(REDIS_URI_KEY, EventAttribute.setValue(uriKey)); + attributeMap.put(REDIS_URI_VALUE, EventAttribute.setValue(uriValue)); + attributeMap.put(REDIS_CAPABILITIES_KEY, EventAttribute.setValue(capabilitiesKey)); + attributeMap.put(REDIS_CAPABILITIES_VALUE, EventAttribute.setValue(capabilitiesJSON)); + attributeMap.put(DATABASE_OPERATION, EventAttribute.setValue("MSET")); + + span.addEvent("Inserted into the database", attributeMap); connection.mset( ImmutableMap.of( uriKey, uriValue, @@ -110,26 +133,38 @@ public Session get(SessionId id) throws NoSuchSessionException { Require.nonNull("Session ID", id); try (Span span = tracer.getCurrentContext().createSpan("GET capabilitiesKey")) { + Map attributeMap = new HashMap<>(); + SESSION_ID.accept(span, id); + SESSION_ID_EVENT.accept(attributeMap, id); setCommonSpanAttributes(span); + setCommonEventAttributes(attributeMap); + span.setAttribute(DATABASE_OPERATION, "GET"); + attributeMap.put(DATABASE_OPERATION, EventAttribute.setValue("GET")); URI uri = getUri(id); + attributeMap.put(REDIS_URI_KEY, EventAttribute.setValue(uriKey(id))); + attributeMap + .put(AttributeKey.SESSION_URI.getKey(), EventAttribute.setValue(uri.toString())); + String capabilitiesKey=capabilitiesKey(id); String rawCapabilities = connection.get(capabilitiesKey); + span.setAttribute(REDIS_CAPABILITIES_KEY, capabilitiesKey); + attributeMap.put(REDIS_CAPABILITIES_KEY, EventAttribute.setValue(capabilitiesKey)); + if(rawCapabilities!=null) { - span.setAttribute(REDIS_CAPABILITIES_KEY, capabilitiesKey); span.setAttribute(REDIS_CAPABILITIES_VALUE, rawCapabilities); } - else - { - span.addEvent("Capabilities do not exist. Received null value from Redis."); - } Capabilities caps = rawCapabilities == null ? new ImmutableCapabilities() : JSON.toType(rawCapabilities, Capabilities.class); + CAPABILITIES.accept(span, caps); + CAPABILITIES_EVENT.accept(attributeMap, caps); + + span.addEvent("Retrieved session from the database", attributeMap); return new Session(id, uri, caps); } } @@ -139,34 +174,48 @@ public URI getUri(SessionId id) throws NoSuchSessionException { Require.nonNull("Session ID", id); try (Span span = tracer.getCurrentContext().createSpan("GET sessionURI")) { + Map attributeMap = new HashMap<>(); + SESSION_ID.accept(span, id); + SESSION_ID_EVENT.accept(attributeMap, id); setCommonSpanAttributes(span); + setCommonEventAttributes(attributeMap); + span.setAttribute(DATABASE_OPERATION, "GET"); + attributeMap.put(DATABASE_OPERATION, EventAttribute.setValue("GET")); String uriKey = uriKey(id); List> rawValues = connection.mget(uriKey); String rawUri = rawValues.get(0).getValueOrElse(null); + span.setAttribute(REDIS_URI_KEY, uriKey); + attributeMap.put(REDIS_URI_KEY, EventAttribute.setValue(uriKey)); + if (rawUri == null) { + NoSuchSessionException exception = new NoSuchSessionException("Unable to find session."); span.setAttribute("error", true); span.setStatus(Status.NOT_FOUND); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Session Id", EventAttribute.setValue(id.toString())); - span.addEvent("Session id does not exist in Redis."); + EXCEPTION.accept(attributeMap, exception); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Session URI does not exist in the database :" + exception.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); - throw new NoSuchSessionException("Unable to find URI for session " + id); + throw exception; } - span.setAttribute(REDIS_URI_KEY, uriKey); span.setAttribute(REDIS_URI_VALUE, rawUri); + attributeMap.put(REDIS_URI_KEY, EventAttribute.setValue(uriKey)); + attributeMap.put(REDIS_URI_VALUE, EventAttribute.setValue(rawUri)); try { return new URI(rawUri); } catch (URISyntaxException e) { span.setAttribute("error", true); span.setStatus(Status.INTERNAL); - Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("Error Message", EventAttribute.setValue(e.getMessage())); - span.addEvent("Unable to convert session id to uri.", attributeValueMap); + EXCEPTION.accept(attributeMap, e); + attributeMap.put(AttributeKey.SESSION_URI.getKey(), EventAttribute.setValue(rawUri)); + attributeMap.put(AttributeKey.EXCEPTION_MESSAGE.getKey(), + EventAttribute.setValue("Unable to convert session id to uri: " + e.getMessage())); + span.addEvent(AttributeKey.EXCEPTION_EVENT.getKey(), attributeMap); throw new NoSuchSessionException(String.format("Unable to convert session id (%s) to uri: %s", id, rawUri), e); } @@ -178,13 +227,22 @@ public void remove(SessionId id) { Require.nonNull("Session ID", id); try (Span span = tracer.getCurrentContext().createSpan("DEL sessionUriKey capabilitiesKey")) { + Map attributeMap = new HashMap<>(); + SESSION_ID.accept(span, id); + SESSION_ID_EVENT.accept(attributeMap, id); setCommonSpanAttributes(span); + setCommonEventAttributes(attributeMap); + span.setAttribute(DATABASE_OPERATION, "DEL"); + attributeMap.put(DATABASE_OPERATION, EventAttribute.setValue("DEL")); String uriKey = uriKey(id); String capabilitiesKey = capabilitiesKey(id); span.setAttribute(REDIS_URI_KEY, uriKey); span.setAttribute(REDIS_CAPABILITIES_KEY, capabilitiesKey); + attributeMap.put(REDIS_URI_KEY, EventAttribute.setValue(uriKey)); + attributeMap.put(REDIS_CAPABILITIES_KEY, EventAttribute.setValue(capabilitiesKey)); + span.addEvent("Deleted session from the database", attributeMap); connection.del(uriKey, capabilitiesKey); } } @@ -208,7 +266,14 @@ private String capabilitiesKey(SessionId id) { private void setCommonSpanAttributes(Span span) { span.setAttribute("span.kind", Span.Kind.CLIENT.toString()); - span.setAttribute("db.system", "redis"); + span.setAttribute(DATABASE_SYSTEM, "redis"); } + private void setCommonEventAttributes(Map map) { + map.put(DATABASE_SYSTEM, EventAttribute.setValue("redis")); + if (serverUri != null) { + map.put(AttributeKey.DATABASE_CONNECTION_STRING.getKey(), + EventAttribute.setValue(serverUri.toString())); + } + } } diff --git a/java/server/src/org/openqa/selenium/grid/web/ProtocolConverter.java b/java/server/src/org/openqa/selenium/grid/web/ProtocolConverter.java index c16a9b5796b13..3e93c03433add 100644 --- a/java/server/src/org/openqa/selenium/grid/web/ProtocolConverter.java +++ b/java/server/src/org/openqa/selenium/grid/web/ProtocolConverter.java @@ -31,6 +31,7 @@ import org.openqa.selenium.remote.JsonToWebElementConverter; import org.openqa.selenium.remote.Response; import org.openqa.selenium.remote.ResponseCodec; +import org.openqa.selenium.remote.SessionId; import org.openqa.selenium.remote.codec.jwp.JsonHttpCommandCodec; import org.openqa.selenium.remote.codec.jwp.JsonHttpResponseCodec; import org.openqa.selenium.remote.codec.w3c.W3CHttpCommandCodec; @@ -39,11 +40,16 @@ import org.openqa.selenium.remote.http.HttpHandler; import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; +import org.openqa.selenium.remote.tracing.AttributeKey; +import org.openqa.selenium.remote.tracing.EventAttribute; +import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.HttpTracing; import org.openqa.selenium.remote.tracing.Span; +import org.openqa.selenium.remote.tracing.Status; import org.openqa.selenium.remote.tracing.Tracer; import java.io.UncheckedIOException; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.Function; @@ -52,9 +58,16 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.openqa.selenium.json.Json.MAP_TYPE; import static org.openqa.selenium.remote.Dialect.W3C; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID; +import static org.openqa.selenium.remote.RemoteTags.SESSION_ID_EVENT; import static org.openqa.selenium.remote.http.Contents.bytes; import static org.openqa.selenium.remote.http.Contents.string; import static org.openqa.selenium.remote.tracing.HttpTracing.newSpanAsChildOf; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST_EVENT; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE_EVENT; +import static org.openqa.selenium.remote.tracing.Tags.KIND; public class ProtocolConverter implements HttpHandler { @@ -104,9 +117,20 @@ public ProtocolConverter( @Override public HttpResponse execute(HttpRequest req) throws UncheckedIOException { try (Span span = newSpanAsChildOf(tracer, req, "protocol_converter")) { + Map attributeMap = new HashMap<>(); + attributeMap.put(AttributeKey.HTTP_HANDLER_CLASS.getKey(), + EventAttribute.setValue(getClass().getName())); Command command = downstream.decode(req); - span.setAttribute("session.id", String.valueOf(command.getSessionId())); - span.setAttribute("command.name", command.getName()); + KIND.accept(span, Span.Kind.SERVER); + HTTP_REQUEST.accept(span, req); + HTTP_REQUEST_EVENT.accept(attributeMap, req); + SessionId sessionId = command.getSessionId(); + SESSION_ID.accept(span, sessionId); + SESSION_ID_EVENT.accept(attributeMap, sessionId); + String commandName = command.getName(); + span.setAttribute("command.name", commandName); + attributeMap.put("command.name", EventAttribute.setValue(commandName)); + attributeMap.put("downstream.command.parameters", EventAttribute.setValue(command.getParameters().toString())); // Massage the webelements @SuppressWarnings("unchecked") @@ -116,12 +140,17 @@ public HttpResponse execute(HttpRequest req) throws UncheckedIOException { command.getName(), parameters); + attributeMap.put("upstream.command.parameters", EventAttribute.setValue(command.getParameters().toString())); HttpRequest request = upstream.encode(command); HttpTracing.inject(tracer, span, request); HttpResponse res = makeRequest(request); - span.setAttribute("http.status", res.getStatus()); - span.setAttribute("error", !res.isSuccessful()); + if(!res.isSuccessful()) { + span.setAttribute("error", true); + span.setStatus(Status.UNKNOWN); + } + HTTP_RESPONSE.accept(span, res); + HTTP_RESPONSE_EVENT.accept(attributeMap, res); HttpResponse toReturn; if (DriverCommand.NEW_SESSION.equals(command.getName()) && res.getStatus() == HTTP_OK) { @@ -137,6 +166,7 @@ public HttpResponse execute(HttpRequest req) throws UncheckedIOException { } }); + span.addEvent("Protocol conversion completed", attributeMap); return toReturn; } } diff --git a/java/server/src/org/openqa/selenium/grid/web/ReverseProxyHandler.java b/java/server/src/org/openqa/selenium/grid/web/ReverseProxyHandler.java index e72aa6ee49c3f..3ca095f9cb9c3 100644 --- a/java/server/src/org/openqa/selenium/grid/web/ReverseProxyHandler.java +++ b/java/server/src/org/openqa/selenium/grid/web/ReverseProxyHandler.java @@ -24,14 +24,23 @@ import org.openqa.selenium.remote.http.HttpHandler; import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; +import org.openqa.selenium.remote.tracing.AttributeKey; +import org.openqa.selenium.remote.tracing.EventAttribute; +import org.openqa.selenium.remote.tracing.EventAttributeValue; import org.openqa.selenium.remote.tracing.Span; import org.openqa.selenium.remote.tracing.Tracer; import java.io.UncheckedIOException; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Logger; import static org.openqa.selenium.remote.tracing.HttpTracing.newSpanAsChildOf; import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_REQUEST_EVENT; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE; +import static org.openqa.selenium.remote.tracing.Tags.HTTP_RESPONSE_EVENT; +import static org.openqa.selenium.remote.tracing.Tags.KIND; public class ReverseProxyHandler implements HttpHandler { @@ -60,7 +69,14 @@ public ReverseProxyHandler(Tracer tracer, HttpClient httpClient) { @Override public HttpResponse execute(HttpRequest req) throws UncheckedIOException { try (Span span = newSpanAsChildOf(tracer, req, "reverse_proxy")) { + + Map attributeMap = new HashMap<>(); + attributeMap.put(AttributeKey.HTTP_HANDLER_CLASS.getKey(), + EventAttribute.setValue(getClass().getName())); + + KIND.accept(span, Span.Kind.SERVER); HTTP_REQUEST.accept(span, req); + HTTP_REQUEST_EVENT.accept(attributeMap, req); HttpRequest toUpstream = new HttpRequest(req.getMethod(), req.getUri()); @@ -85,7 +101,8 @@ public HttpResponse execute(HttpRequest req) throws UncheckedIOException { toUpstream.setContent(req.getContent()); HttpResponse resp = upstream.execute(toUpstream); - span.setAttribute("http.status", resp.getStatus()); + HTTP_RESPONSE.accept(span,resp); + HTTP_RESPONSE_EVENT.accept(attributeMap, resp); // clear response defaults. resp.removeHeader("Date"); @@ -93,6 +110,7 @@ public HttpResponse execute(HttpRequest req) throws UncheckedIOException { IGNORED_REQ_HEADERS.forEach(resp::removeHeader); + span.addEvent("HTTP request execution complete", attributeMap); return resp; } }