Automatic tag table generation
Without this change we don't even really know how many tags are created and what are their values. With this change we want this information to be automatically rendered.
This commit is contained in:
@@ -1,222 +1,222 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth;
|
||||
|
||||
import org.springframework.cloud.sleuth.propagation.Propagator;
|
||||
|
||||
/**
|
||||
*
|
||||
* This API was heavily influenced by Brave. Parts of its documentation were taken
|
||||
* directly from Brave.
|
||||
*
|
||||
* Span is a single unit of work that needs to be started and stopped. Contains timing
|
||||
* information and events and tags.
|
||||
*
|
||||
* @author OpenZipkin Brave Authors
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public interface Span extends SpanCustomizer {
|
||||
|
||||
/**
|
||||
* @return {@code true} when no recording is done and nothing is reported to an
|
||||
* external system. However, this span should still be injected into outgoing
|
||||
* requests. Use this flag to avoid performing expensive computation.
|
||||
*/
|
||||
boolean isNoop();
|
||||
|
||||
/**
|
||||
* @return {@link TraceContext} corresponding to this span.
|
||||
*/
|
||||
TraceContext context();
|
||||
|
||||
/**
|
||||
* Starts this span.
|
||||
* @return this span
|
||||
*/
|
||||
Span start();
|
||||
|
||||
/**
|
||||
* Sets a name on this span.
|
||||
* @param name name to set on the span
|
||||
* @return this span
|
||||
*/
|
||||
Span name(String name);
|
||||
|
||||
/**
|
||||
* Sets an event on this span.
|
||||
* @param value event name to set on the span
|
||||
* @return this span
|
||||
*/
|
||||
Span event(String value);
|
||||
|
||||
/**
|
||||
* Sets a tag on this span.
|
||||
* @param key tag key
|
||||
* @param value tag value
|
||||
* @return this span
|
||||
*/
|
||||
Span tag(String key, String value);
|
||||
|
||||
/**
|
||||
* Records an exception for this span.
|
||||
* @param throwable to record
|
||||
* @return this span
|
||||
*/
|
||||
Span error(Throwable throwable);
|
||||
|
||||
/**
|
||||
* Ends the span. The span gets stopped and recorded if not noop.
|
||||
*/
|
||||
void end();
|
||||
|
||||
/**
|
||||
* Ends the span. The span gets stopped but does not get recorded.
|
||||
*/
|
||||
void abandon();
|
||||
|
||||
/**
|
||||
* Sets the remote service name for the span.
|
||||
* @param remoteServiceName remote service name
|
||||
* @return this span
|
||||
* @since 3.0.3
|
||||
*/
|
||||
default Span remoteServiceName(String remoteServiceName) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of span. Can be used to specify additional relationships between spans in
|
||||
* addition to a parent/child relationship.
|
||||
*
|
||||
* Documentation of the enum taken from OpenTelemetry.
|
||||
*/
|
||||
enum Kind {
|
||||
|
||||
/**
|
||||
* Indicates that the span covers server-side handling of an RPC or other remote
|
||||
* request.
|
||||
*/
|
||||
SERVER,
|
||||
|
||||
/**
|
||||
* Indicates that the span covers the client-side wrapper around an RPC or other
|
||||
* remote request.
|
||||
*/
|
||||
CLIENT,
|
||||
|
||||
/**
|
||||
* Indicates that the span describes producer sending a message to a broker.
|
||||
* Unlike client and server, there is no direct critical path latency relationship
|
||||
* between producer and consumer spans.
|
||||
*/
|
||||
PRODUCER,
|
||||
|
||||
/**
|
||||
* Indicates that the span describes consumer receiving a message from a broker.
|
||||
* Unlike client and server, there is no direct critical path latency relationship
|
||||
* between producer and consumer spans.
|
||||
*/
|
||||
CONSUMER
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* In some cases (e.g. when dealing with
|
||||
* {@link Propagator#extract(Object, Propagator.Getter)}'s we want to create a span
|
||||
* that has not yet been started, yet it's heavily configurable (some options are not
|
||||
* possible to be set when a span has already been started). We can achieve that by
|
||||
* using a builder.
|
||||
*
|
||||
* Inspired by OpenZipkin Brave and OpenTelemetry API.
|
||||
*/
|
||||
interface Builder {
|
||||
|
||||
/**
|
||||
* Sets the parent of the built span.
|
||||
* @param context parent's context
|
||||
* @return this
|
||||
*/
|
||||
Builder setParent(TraceContext context);
|
||||
|
||||
/**
|
||||
* Sets no parent of the built span.
|
||||
* @return this
|
||||
*/
|
||||
Builder setNoParent();
|
||||
|
||||
/**
|
||||
* Sets the name of the span.
|
||||
* @param name span name
|
||||
* @return this
|
||||
*/
|
||||
Builder name(String name);
|
||||
|
||||
/**
|
||||
* Sets an event on the span.
|
||||
* @param value event value
|
||||
* @return this
|
||||
*/
|
||||
Builder event(String value);
|
||||
|
||||
/**
|
||||
* Sets a tag on the span.
|
||||
* @param key tag key
|
||||
* @param value tag value
|
||||
* @return this
|
||||
*/
|
||||
Builder tag(String key, String value);
|
||||
|
||||
/**
|
||||
* Sets an error on the span.
|
||||
* @param throwable error to set
|
||||
* @return this
|
||||
*/
|
||||
Builder error(Throwable throwable);
|
||||
|
||||
/**
|
||||
* Sets the kind on the span.
|
||||
* @param spanKind kind of the span
|
||||
* @return this
|
||||
*/
|
||||
Builder kind(Span.Kind spanKind);
|
||||
|
||||
/**
|
||||
* Sets the remote service name for the span.
|
||||
* @param remoteServiceName remote service name
|
||||
* @return this
|
||||
*/
|
||||
Builder remoteServiceName(String remoteServiceName);
|
||||
|
||||
/**
|
||||
* Sets the remote URL for the span.
|
||||
* @param remoteUrl remote service name
|
||||
* @return this
|
||||
*/
|
||||
default Builder remoteUrl(String remoteUrl) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and starts the span.
|
||||
* @return started span
|
||||
*/
|
||||
Span start();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth;
|
||||
|
||||
import org.springframework.cloud.sleuth.propagation.Propagator;
|
||||
|
||||
/**
|
||||
*
|
||||
* This API was heavily influenced by Brave. Parts of its documentation were taken
|
||||
* directly from Brave.
|
||||
*
|
||||
* Span is a single unit of work that needs to be started and stopped. Contains timing
|
||||
* information and events and tags.
|
||||
*
|
||||
* @author OpenZipkin Brave Authors
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public interface Span extends SpanCustomizer {
|
||||
|
||||
/**
|
||||
* @return {@code true} when no recording is done and nothing is reported to an
|
||||
* external system. However, this span should still be injected into outgoing
|
||||
* requests. Use this flag to avoid performing expensive computation.
|
||||
*/
|
||||
boolean isNoop();
|
||||
|
||||
/**
|
||||
* @return {@link TraceContext} corresponding to this span.
|
||||
*/
|
||||
TraceContext context();
|
||||
|
||||
/**
|
||||
* Starts this span.
|
||||
* @return this span
|
||||
*/
|
||||
Span start();
|
||||
|
||||
/**
|
||||
* Sets a name on this span.
|
||||
* @param name name to set on the span
|
||||
* @return this span
|
||||
*/
|
||||
Span name(String name);
|
||||
|
||||
/**
|
||||
* Sets an event on this span.
|
||||
* @param value event name to set on the span
|
||||
* @return this span
|
||||
*/
|
||||
Span event(String value);
|
||||
|
||||
/**
|
||||
* Sets a tag on this span.
|
||||
* @param key tag key
|
||||
* @param value tag value
|
||||
* @return this span
|
||||
*/
|
||||
Span tag(String key, String value);
|
||||
|
||||
/**
|
||||
* Records an exception for this span.
|
||||
* @param throwable to record
|
||||
* @return this span
|
||||
*/
|
||||
Span error(Throwable throwable);
|
||||
|
||||
/**
|
||||
* Ends the span. The span gets stopped and recorded if not noop.
|
||||
*/
|
||||
void end();
|
||||
|
||||
/**
|
||||
* Ends the span. The span gets stopped but does not get recorded.
|
||||
*/
|
||||
void abandon();
|
||||
|
||||
/**
|
||||
* Sets the remote service name for the span.
|
||||
* @param remoteServiceName remote service name
|
||||
* @return this span
|
||||
* @since 3.0.3
|
||||
*/
|
||||
default Span remoteServiceName(String remoteServiceName) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of span. Can be used to specify additional relationships between spans in
|
||||
* addition to a parent/child relationship.
|
||||
*
|
||||
* Documentation of the enum taken from OpenTelemetry.
|
||||
*/
|
||||
enum Kind {
|
||||
|
||||
/**
|
||||
* Indicates that the span covers server-side handling of an RPC or other remote
|
||||
* request.
|
||||
*/
|
||||
SERVER,
|
||||
|
||||
/**
|
||||
* Indicates that the span covers the client-side wrapper around an RPC or other
|
||||
* remote request.
|
||||
*/
|
||||
CLIENT,
|
||||
|
||||
/**
|
||||
* Indicates that the span describes producer sending a message to a broker.
|
||||
* Unlike client and server, there is no direct critical path latency relationship
|
||||
* between producer and consumer spans.
|
||||
*/
|
||||
PRODUCER,
|
||||
|
||||
/**
|
||||
* Indicates that the span describes consumer receiving a message from a broker.
|
||||
* Unlike client and server, there is no direct critical path latency relationship
|
||||
* between producer and consumer spans.
|
||||
*/
|
||||
CONSUMER
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* In some cases (e.g. when dealing with
|
||||
* {@link Propagator#extract(Object, Propagator.Getter)}'s we want to create a span
|
||||
* that has not yet been started, yet it's heavily configurable (some options are not
|
||||
* possible to be set when a span has already been started). We can achieve that by
|
||||
* using a builder.
|
||||
*
|
||||
* Inspired by OpenZipkin Brave and OpenTelemetry API.
|
||||
*/
|
||||
interface Builder {
|
||||
|
||||
/**
|
||||
* Sets the parent of the built span.
|
||||
* @param context parent's context
|
||||
* @return this
|
||||
*/
|
||||
Builder setParent(TraceContext context);
|
||||
|
||||
/**
|
||||
* Sets no parent of the built span.
|
||||
* @return this
|
||||
*/
|
||||
Builder setNoParent();
|
||||
|
||||
/**
|
||||
* Sets the name of the span.
|
||||
* @param name span name
|
||||
* @return this
|
||||
*/
|
||||
Builder name(String name);
|
||||
|
||||
/**
|
||||
* Sets an event on the span.
|
||||
* @param value event value
|
||||
* @return this
|
||||
*/
|
||||
Builder event(String value);
|
||||
|
||||
/**
|
||||
* Sets a tag on the span.
|
||||
* @param key tag key
|
||||
* @param value tag value
|
||||
* @return this
|
||||
*/
|
||||
Builder tag(String key, String value);
|
||||
|
||||
/**
|
||||
* Sets an error on the span.
|
||||
* @param throwable error to set
|
||||
* @return this
|
||||
*/
|
||||
Builder error(Throwable throwable);
|
||||
|
||||
/**
|
||||
* Sets the kind on the span.
|
||||
* @param spanKind kind of the span
|
||||
* @return this
|
||||
*/
|
||||
Builder kind(Span.Kind spanKind);
|
||||
|
||||
/**
|
||||
* Sets the remote service name for the span.
|
||||
* @param remoteServiceName remote service name
|
||||
* @return this
|
||||
*/
|
||||
Builder remoteServiceName(String remoteServiceName);
|
||||
|
||||
/**
|
||||
* Sets the remote URL for the span.
|
||||
* @param remoteUrl remote service name
|
||||
* @return this
|
||||
*/
|
||||
default Builder remoteUrl(String remoteUrl) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and starts the span.
|
||||
* @return started span
|
||||
*/
|
||||
Span start();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth;
|
||||
|
||||
/**
|
||||
*
|
||||
* Describes the behaviour of an object that can be tagged.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.0.3
|
||||
*/
|
||||
public interface Taggable {
|
||||
|
||||
/**
|
||||
* Sets a tag.
|
||||
* @param key tag key
|
||||
* @param value tag value
|
||||
* @return this, for chaining
|
||||
*/
|
||||
Taggable tag(String key, String value);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.TraceContext;
|
||||
|
||||
/**
|
||||
* {@link Span} that performs additional assertions such as allowed name, tag, event
|
||||
* verification and upon reporting, whether the span had been started in the first place.
|
||||
*
|
||||
* You need to turn on assertions via system properties or environment variables to start
|
||||
* breaking your tests or production code. Check {@link DocumentedSpanAssertions} for more
|
||||
* information.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface AssertingSpan extends Span {
|
||||
|
||||
/**
|
||||
* @return a {@link DocumentedSpan} with span configuration
|
||||
*/
|
||||
DocumentedSpan getDocumentedSpan();
|
||||
|
||||
/**
|
||||
* @return wrapped {@link Span}
|
||||
*/
|
||||
Span getDelegate();
|
||||
|
||||
/**
|
||||
* @return {@code true} when this span was started
|
||||
*/
|
||||
default boolean isStarted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpan tag(String key, String value) {
|
||||
DocumentedSpanAssertions.assertThatKeyIsValid(key, getDocumentedSpan());
|
||||
getDelegate().tag(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tags a span via {@link TagKey}.
|
||||
* @param key tag key
|
||||
* @param value tag value
|
||||
* @return this for chaining
|
||||
*/
|
||||
default AssertingSpan tag(TagKey key, String value) {
|
||||
DocumentedSpanAssertions.assertThatKeyIsValid(key, getDocumentedSpan());
|
||||
getDelegate().tag(key.getKey(), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpan event(String value) {
|
||||
DocumentedSpanAssertions.assertThatEventIsValid(value, getDocumentedSpan());
|
||||
getDelegate().event(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotates with an event via {@link EventValue}.
|
||||
* @param value event value
|
||||
* @return this for chaining
|
||||
*/
|
||||
default AssertingSpan event(EventValue value) {
|
||||
DocumentedSpanAssertions.assertThatEventIsValid(value, getDocumentedSpan());
|
||||
getDelegate().event(value.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpan name(String name) {
|
||||
DocumentedSpanAssertions.assertThatNameIsValid(name, getDocumentedSpan());
|
||||
getDelegate().name(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isNoop() {
|
||||
return getDelegate().isNoop();
|
||||
}
|
||||
|
||||
@Override
|
||||
default TraceContext context() {
|
||||
return getDelegate().context();
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpan start() {
|
||||
getDelegate().start();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpan error(Throwable throwable) {
|
||||
getDelegate().error(throwable);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void end() {
|
||||
DocumentedSpanAssertions.assertThatSpanStartedBeforeEnd(this);
|
||||
getDelegate().end();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void abandon() {
|
||||
getDelegate().abandon();
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpan remoteServiceName(String remoteServiceName) {
|
||||
getDelegate().remoteServiceName(remoteServiceName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param documentedSpan span configuration
|
||||
* @param span span to wrap in assertions
|
||||
* @return asserting span
|
||||
*/
|
||||
static AssertingSpan of(DocumentedSpan documentedSpan, Span span) {
|
||||
if (span == null) {
|
||||
return null;
|
||||
}
|
||||
else if (span instanceof AssertingSpan) {
|
||||
return (AssertingSpan) span;
|
||||
}
|
||||
return new ImmutableAssertingSpan(documentedSpan, span);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying delegate. Used when casting is necessary.
|
||||
* @param span span to check for wrapping
|
||||
* @param <T> type extending a span
|
||||
* @return unwrapped object
|
||||
*/
|
||||
static <T extends Span> T unwrap(Span span) {
|
||||
if (span == null) {
|
||||
return null;
|
||||
}
|
||||
else if (span instanceof AssertingSpan) {
|
||||
return (T) ((AssertingSpan) span).getDelegate();
|
||||
}
|
||||
return (T) span;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.TraceContext;
|
||||
|
||||
/**
|
||||
* A {@link Span.Builder} that can perform assertions on itself.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface AssertingSpanBuilder extends Span.Builder {
|
||||
|
||||
/**
|
||||
* @return a {@link DocumentedSpan} with span configuration
|
||||
*/
|
||||
DocumentedSpan getDocumentedSpan();
|
||||
|
||||
/**
|
||||
* @return wrapped {@link Span.Builder}
|
||||
*/
|
||||
Span.Builder getDelegate();
|
||||
|
||||
@Override
|
||||
default AssertingSpanBuilder tag(String key, String value) {
|
||||
DocumentedSpanAssertions.assertThatKeyIsValid(key, getDocumentedSpan());
|
||||
getDelegate().tag(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a tag on a span.
|
||||
* @param key tag key
|
||||
* @param value tag
|
||||
* @return this, for chaining
|
||||
*/
|
||||
default AssertingSpanBuilder tag(TagKey key, String value) {
|
||||
DocumentedSpanAssertions.assertThatKeyIsValid(key, getDocumentedSpan());
|
||||
getDelegate().tag(key.getKey(), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpanBuilder event(String value) {
|
||||
DocumentedSpanAssertions.assertThatEventIsValid(value, getDocumentedSpan());
|
||||
getDelegate().event(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an event on a span.
|
||||
* @param value event
|
||||
* @return this, for chaining
|
||||
*/
|
||||
default AssertingSpanBuilder event(EventValue value) {
|
||||
DocumentedSpanAssertions.assertThatEventIsValid(value, getDocumentedSpan());
|
||||
getDelegate().event(value.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpanBuilder name(String name) {
|
||||
DocumentedSpanAssertions.assertThatNameIsValid(name, getDocumentedSpan());
|
||||
getDelegate().name(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpanBuilder error(Throwable throwable) {
|
||||
getDelegate().error(throwable);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpanBuilder remoteServiceName(String remoteServiceName) {
|
||||
getDelegate().remoteServiceName(remoteServiceName);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpanBuilder setParent(TraceContext context) {
|
||||
getDelegate().setParent(context);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpanBuilder setNoParent() {
|
||||
getDelegate().setNoParent();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpanBuilder kind(Span.Kind spanKind) {
|
||||
getDelegate().kind(spanKind);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpan start() {
|
||||
Span span = getDelegate().start();
|
||||
DocumentedSpan documentedSpan = getDocumentedSpan();
|
||||
return new AssertingSpan() {
|
||||
@Override
|
||||
public DocumentedSpan getDocumentedSpan() {
|
||||
return documentedSpan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span getDelegate() {
|
||||
return span;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param documentedSpan span configuration
|
||||
* @param builder builder to wrap in assertions
|
||||
* @return asserting span builder
|
||||
*/
|
||||
static AssertingSpanBuilder of(DocumentedSpan documentedSpan, Span.Builder builder) {
|
||||
if (builder == null) {
|
||||
return null;
|
||||
}
|
||||
else if (builder instanceof AssertingSpanBuilder) {
|
||||
return (AssertingSpanBuilder) builder;
|
||||
}
|
||||
return new ImmutableAssertingSpanBuilder(documentedSpan, builder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
import org.springframework.cloud.sleuth.SpanCustomizer;
|
||||
|
||||
/**
|
||||
* A {@link SpanCustomizer} that can perform assertions on itself.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface AssertingSpanCustomizer extends SpanCustomizer {
|
||||
|
||||
/**
|
||||
* @return a {@link DocumentedSpan} with span configuration
|
||||
*/
|
||||
DocumentedSpan getDocumentedSpan();
|
||||
|
||||
/**
|
||||
* @return wrapped {@link SpanCustomizer}
|
||||
*/
|
||||
SpanCustomizer getDelegate();
|
||||
|
||||
@Override
|
||||
default AssertingSpanCustomizer tag(String key, String value) {
|
||||
DocumentedSpanAssertions.assertThatKeyIsValid(key, getDocumentedSpan());
|
||||
getDelegate().tag(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a tag on a span.
|
||||
* @param key tag key
|
||||
* @param value tag
|
||||
* @return this, for chaining
|
||||
*/
|
||||
default AssertingSpanCustomizer tag(TagKey key, String value) {
|
||||
DocumentedSpanAssertions.assertThatKeyIsValid(key, getDocumentedSpan());
|
||||
getDelegate().tag(key.getKey(), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpanCustomizer event(String value) {
|
||||
DocumentedSpanAssertions.assertThatEventIsValid(value, getDocumentedSpan());
|
||||
getDelegate().event(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an event on a span.
|
||||
* @param value event
|
||||
* @return this, for chaining
|
||||
*/
|
||||
default AssertingSpanCustomizer event(EventValue value) {
|
||||
DocumentedSpanAssertions.assertThatEventIsValid(value, getDocumentedSpan());
|
||||
getDelegate().event(value.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default AssertingSpanCustomizer name(String name) {
|
||||
DocumentedSpanAssertions.assertThatNameIsValid(name, getDocumentedSpan());
|
||||
getDelegate().name(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param documentedSpan span configuration
|
||||
* @param span span to wrap in assertions
|
||||
* @return asserting span customizer
|
||||
*/
|
||||
static AssertingSpanCustomizer of(DocumentedSpan documentedSpan, SpanCustomizer span) {
|
||||
if (span instanceof AssertingSpanCustomizer) {
|
||||
return (AssertingSpanCustomizer) span;
|
||||
}
|
||||
return new ImmutableAssertingSpanCustomizer(documentedSpan, span);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying delegate. Used when casting is necessary.
|
||||
* @param span span to check for wrapping
|
||||
* @param <T> type extending a span
|
||||
* @return unwrapped object
|
||||
*/
|
||||
static <T extends SpanCustomizer> T unwrap(SpanCustomizer span) {
|
||||
if (span == null) {
|
||||
return null;
|
||||
}
|
||||
else if (span instanceof AssertingSpanCustomizer) {
|
||||
return (T) ((AssertingSpanCustomizer) span).getDelegate();
|
||||
}
|
||||
return (T) span;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanCustomizer;
|
||||
|
||||
/**
|
||||
* In order to describe your spans via e.g. enums instead of Strings you can use this
|
||||
* interface that returns all the characteristics of a span. In Spring Cloud Sleuth we
|
||||
* analyze the sources and reuse this information to build a table of known spans, their
|
||||
* names, tags and events.
|
||||
*
|
||||
* We can generate documentation for all created spans but certain requirements need to be
|
||||
* met
|
||||
*
|
||||
* - spans are grouped within an enum - the enum implements the {@link DocumentedSpan}
|
||||
* interface - if the span contains {@link TagKey} or {@link EventValue} then those need
|
||||
* to be declared as nested enums - the {@link DocumentedSpan#getTagKeys()} and
|
||||
* {@link DocumentedSpan#getEvents()} need to call the nested enum's {@code values()}
|
||||
* method to retrieve the array of allowed keys / events
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface DocumentedSpan {
|
||||
|
||||
/**
|
||||
* @return span name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* @return allowed tag keys
|
||||
*/
|
||||
default TagKey[] getTagKeys() {
|
||||
return new TagKey[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return allowed events
|
||||
*/
|
||||
default EventValue[] getEvents() {
|
||||
return new EventValue[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns required prefix to be there for events and tags. Example {@code foo.} would
|
||||
* require the tags and events to have a {code foo} prefix like this for tags:
|
||||
* {@code foo.bar=true} and {@code foo.started} for events.
|
||||
* @return required prefix
|
||||
*/
|
||||
default String prefix() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts on tags, names and allowed events.
|
||||
* @param span to wrap
|
||||
* @return wrapped span
|
||||
*/
|
||||
default AssertingSpan wrap(Span span) {
|
||||
if (span == null) {
|
||||
return null;
|
||||
}
|
||||
else if (span instanceof AssertingSpan) {
|
||||
return (AssertingSpan) span;
|
||||
}
|
||||
return AssertingSpan.of(this, span);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts on tags, names and allowed events.
|
||||
* @param span to wrap
|
||||
* @return wrapped span
|
||||
*/
|
||||
default AssertingSpanCustomizer wrap(SpanCustomizer span) {
|
||||
if (span == null) {
|
||||
return null;
|
||||
}
|
||||
else if (span instanceof AssertingSpanCustomizer) {
|
||||
return (AssertingSpanCustomizer) span;
|
||||
}
|
||||
return AssertingSpanCustomizer.of(this, span);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts on tags, names and allowed events.
|
||||
* @param span builder to wrap
|
||||
* @return wrapped span
|
||||
*/
|
||||
default AssertingSpanBuilder wrap(Span.Builder span) {
|
||||
if (span == null) {
|
||||
return null;
|
||||
}
|
||||
else if (span instanceof AssertingSpanBuilder) {
|
||||
return (AssertingSpanBuilder) span;
|
||||
}
|
||||
return AssertingSpanBuilder.of(this, span);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* In order to turn on the assertions you need to either turn on the
|
||||
* {@code spring.cloud.sleuth.assertions.enabled} system property or
|
||||
* {@code SPRING_CLOUD_SLEUTH_ASSERTIONS_ENABLED} environment variable.
|
||||
*/
|
||||
final class DocumentedSpanAssertions {
|
||||
|
||||
static boolean SLEUTH_SPAN_ASSERTIONS_ON = Boolean.parseBoolean(System.getProperty(
|
||||
"spring.cloud.sleuth.assertions.enabled", System.getenv("SPRING_CLOUD_SLEUTH_ASSERTIONS_ENABLED") != null
|
||||
? System.getenv("SPRING_CLOUD_SLEUTH_ASSERTIONS_ENABLED") : "false"));
|
||||
|
||||
private static final Map<String, Pattern> PATTERN_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\].+*?^$\\\\|]");
|
||||
|
||||
private DocumentedSpanAssertions() {
|
||||
throw new IllegalStateException("Can't instantiate utility class");
|
||||
}
|
||||
|
||||
static void assertThatKeyIsValid(String key, DocumentedSpan documentedSpan) {
|
||||
if (SLEUTH_SPAN_ASSERTIONS_ON) {
|
||||
TagKey[] allowedKeys = documentedSpan.getTagKeys();
|
||||
if (allowedKeys.length == 0) {
|
||||
return;
|
||||
}
|
||||
boolean validTagKey = Arrays.stream(allowedKeys)
|
||||
.anyMatch(tagKey -> patternOrValueMatches(key, tagKey.getKey())
|
||||
&& hasRequiredPrefix(key, documentedSpan.prefix()));
|
||||
if (!validTagKey) {
|
||||
throw new AssertionError("The key [" + key + "] is invalid. You can use only one matching "
|
||||
+ Arrays.stream(allowedKeys).map(TagKey::getKey).collect(Collectors.toList())
|
||||
+ prefixWarningIfPresent(documentedSpan));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String prefixWarningIfPresent(DocumentedSpan documentedSpan) {
|
||||
return StringUtils.hasText(documentedSpan.prefix())
|
||||
? ". Also it has start with [" + documentedSpan.prefix() + "] prefix" : "";
|
||||
}
|
||||
|
||||
static void assertThatKeyIsValid(TagKey key, DocumentedSpan documentedSpan) {
|
||||
if (SLEUTH_SPAN_ASSERTIONS_ON) {
|
||||
TagKey[] allowedKeys = documentedSpan.getTagKeys();
|
||||
if (allowedKeys.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (Arrays.stream(allowedKeys).noneMatch(tagKey -> patternOrValueMatches(key.getKey(), tagKey.getKey())
|
||||
&& hasRequiredPrefix(key.getKey(), documentedSpan.prefix()))) {
|
||||
throw new AssertionError("The key [" + key.getKey() + "] is invalid. You can use only one matching "
|
||||
+ Arrays.stream(allowedKeys).map(TagKey::getKey).collect(Collectors.toList())
|
||||
+ prefixWarningIfPresent(documentedSpan));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void assertThatNameIsValid(String name, DocumentedSpan documentedSpan) {
|
||||
String allowedName = documentedSpan.getName();
|
||||
if (SLEUTH_SPAN_ASSERTIONS_ON && !patternOrValueMatches(name, allowedName)) {
|
||||
throw new AssertionError(
|
||||
"The name [" + name + "] is invalid. You can use only one matching [" + allowedName + "]");
|
||||
}
|
||||
}
|
||||
|
||||
static void assertThatEventIsValid(String eventValue, DocumentedSpan documentedSpan) {
|
||||
if (SLEUTH_SPAN_ASSERTIONS_ON) {
|
||||
EventValue[] allowed = documentedSpan.getEvents();
|
||||
if (allowed.length == 0) {
|
||||
return;
|
||||
}
|
||||
boolean valid = Arrays.stream(allowed).anyMatch(value -> patternOrValueMatches(eventValue, value.getValue())
|
||||
&& hasRequiredPrefix(eventValue, documentedSpan.prefix()));
|
||||
if (!valid) {
|
||||
throw new AssertionError("The event [" + eventValue + "] is invalid. You can use only one matching "
|
||||
+ Arrays.stream(allowed).map(EventValue::getValue).collect(Collectors.toList())
|
||||
+ prefixWarningIfPresent(documentedSpan));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void assertThatEventIsValid(EventValue eventValue, DocumentedSpan documentedSpan) {
|
||||
if (SLEUTH_SPAN_ASSERTIONS_ON) {
|
||||
EventValue[] allowed = documentedSpan.getEvents();
|
||||
if (allowed.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (Arrays.stream(allowed).noneMatch(value -> patternOrValueMatches(eventValue.getValue(), value.getValue())
|
||||
&& hasRequiredPrefix(eventValue.getValue(), documentedSpan.prefix()))) {
|
||||
throw new AssertionError(
|
||||
"The event [" + eventValue.getValue() + "] is invalid. You can use only one matching "
|
||||
+ Arrays.stream(allowed).map(EventValue::getValue).collect(Collectors.toList())
|
||||
+ prefixWarningIfPresent(documentedSpan));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void assertThatSpanStartedBeforeEnd(AssertingSpan span) {
|
||||
if (SLEUTH_SPAN_ASSERTIONS_ON && !span.isStarted()) {
|
||||
throw new AssertionError("The span was not started, however you're trying to end it");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean patternOrValueMatches(String pickedValue, String allowedValue) {
|
||||
if (allowedValue.contains("%s")) {
|
||||
String stringPattern = escapeSpecialRegexWithSingleEscape(allowedValue).replaceAll("%s", ".*?");
|
||||
Pattern pattern = PATTERN_CACHE.computeIfAbsent(stringPattern, Pattern::compile);
|
||||
return pattern.matcher(pickedValue).matches();
|
||||
}
|
||||
return allowedValue.equals(pickedValue);
|
||||
}
|
||||
|
||||
private static boolean hasRequiredPrefix(String value, String prefix) {
|
||||
if (StringUtils.hasText(prefix)) {
|
||||
return value.startsWith(prefix);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String escapeSpecialRegexWithSingleEscape(String str) {
|
||||
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
/**
|
||||
* Event value representing a notable event in time.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface EventValue {
|
||||
|
||||
/**
|
||||
* @return event value
|
||||
*/
|
||||
String getValue();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
class ImmutableAssertingSpan implements AssertingSpan {
|
||||
|
||||
private final DocumentedSpan documentedSpan;
|
||||
|
||||
private final Span delegate;
|
||||
|
||||
boolean isStarted;
|
||||
|
||||
ImmutableAssertingSpan(DocumentedSpan documentedSpan, Span delegate) {
|
||||
requireNonNull(documentedSpan);
|
||||
requireNonNull(delegate);
|
||||
this.documentedSpan = documentedSpan;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ImmutableAssertingSpan that = (ImmutableAssertingSpan) o;
|
||||
return Objects.equals(documentedSpan, that.documentedSpan) && Objects.equals(delegate, that.delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.delegate.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(documentedSpan, delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentedSpan getDocumentedSpan() {
|
||||
return this.documentedSpan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span getDelegate() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssertingSpan start() {
|
||||
this.isStarted = true;
|
||||
return AssertingSpan.super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return this.isStarted;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
class ImmutableAssertingSpanBuilder implements AssertingSpanBuilder {
|
||||
|
||||
private final DocumentedSpan documentedSpan;
|
||||
|
||||
private final Span.Builder delegate;
|
||||
|
||||
ImmutableAssertingSpanBuilder(DocumentedSpan documentedSpan, Span.Builder delegate) {
|
||||
requireNonNull(documentedSpan);
|
||||
requireNonNull(delegate);
|
||||
this.documentedSpan = documentedSpan;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ImmutableAssertingSpanBuilder that = (ImmutableAssertingSpanBuilder) o;
|
||||
return Objects.equals(documentedSpan, that.documentedSpan) && Objects.equals(delegate, that.delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.delegate.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(documentedSpan, delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentedSpan getDocumentedSpan() {
|
||||
return this.documentedSpan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span.Builder getDelegate() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.cloud.sleuth.SpanCustomizer;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
class ImmutableAssertingSpanCustomizer implements AssertingSpanCustomizer {
|
||||
|
||||
private final DocumentedSpan documentedSpan;
|
||||
|
||||
private final SpanCustomizer delegate;
|
||||
|
||||
ImmutableAssertingSpanCustomizer(DocumentedSpan documentedSpan, SpanCustomizer delegate) {
|
||||
requireNonNull(documentedSpan);
|
||||
requireNonNull(delegate);
|
||||
this.documentedSpan = documentedSpan;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ImmutableAssertingSpanCustomizer that = (ImmutableAssertingSpanCustomizer) o;
|
||||
return Objects.equals(documentedSpan, that.documentedSpan) && Objects.equals(delegate, that.delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(documentedSpan, delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentedSpan getDocumentedSpan() {
|
||||
return this.documentedSpan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpanCustomizer getDelegate() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represents a tag key.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface TagKey {
|
||||
|
||||
/**
|
||||
* @return tag key
|
||||
*/
|
||||
String getKey();
|
||||
|
||||
/**
|
||||
* Merges arrays of tags.
|
||||
* @param tags array of tags
|
||||
* @return a merged array of tags
|
||||
*/
|
||||
static TagKey[] merge(TagKey[]... tags) {
|
||||
return Arrays.stream(tags).flatMap(Arrays::stream).toArray(TagKey[]::new);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.cloud.sleuth.docs;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.BDDMockito;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.thenThrownBy;
|
||||
import static org.springframework.cloud.sleuth.docs.DocumentedSpanAssertions.assertThatEventIsValid;
|
||||
import static org.springframework.cloud.sleuth.docs.DocumentedSpanAssertions.assertThatKeyIsValid;
|
||||
import static org.springframework.cloud.sleuth.docs.DocumentedSpanAssertions.assertThatNameIsValid;
|
||||
import static org.springframework.cloud.sleuth.docs.DocumentedSpanAssertions.assertThatSpanStartedBeforeEnd;
|
||||
import static org.springframework.cloud.sleuth.docs.DocumentedSpanAssertionsTests.MyEventsWithNotMatchingPrefix.A_BAR_EVENT;
|
||||
import static org.springframework.cloud.sleuth.docs.DocumentedSpanAssertionsTests.MySpan.SPAN_WITH_DYNAMIC_ENTRIES;
|
||||
import static org.springframework.cloud.sleuth.docs.DocumentedSpanAssertionsTests.MySpan.SPAN_WITH_EMPTY_TAGS_AND_EVENTS;
|
||||
import static org.springframework.cloud.sleuth.docs.DocumentedSpanAssertionsTests.MySpan.SPAN_WITH_NOT_MATCHING_PREFIX;
|
||||
import static org.springframework.cloud.sleuth.docs.DocumentedSpanAssertionsTests.MySpan.SPAN_WITH_PREFIX;
|
||||
import static org.springframework.cloud.sleuth.docs.DocumentedSpanAssertionsTests.MyTags.A_FOO_TAG;
|
||||
|
||||
class DocumentedSpanAssertionsTests {
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
DocumentedSpanAssertions.SLEUTH_SPAN_ASSERTIONS_ON = true;
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_do_nothing_when_system_property_not_turned_on() {
|
||||
DocumentedSpanAssertions.SLEUTH_SPAN_ASSERTIONS_ON = false;
|
||||
|
||||
assertThatKeyIsValid("unknown_key", SPAN_WITH_NOT_MATCHING_PREFIX);
|
||||
assertThatKeyIsValid(A_FOO_TAG, SPAN_WITH_PREFIX);
|
||||
assertThatEventIsValid("unknown_event", SPAN_WITH_PREFIX);
|
||||
assertThatEventIsValid(A_BAR_EVENT, SPAN_WITH_PREFIX);
|
||||
assertThatNameIsValid("unknown_name", SPAN_WITH_NOT_MATCHING_PREFIX);
|
||||
assertThatSpanStartedBeforeEnd(
|
||||
new ImmutableAssertingSpan(SPAN_WITH_NOT_MATCHING_PREFIX, BDDMockito.mock(Span.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_do_nothing_when_tags_or_events_are_empty() {
|
||||
assertThatKeyIsValid("unknown_key", SPAN_WITH_EMPTY_TAGS_AND_EVENTS);
|
||||
assertThatKeyIsValid(A_FOO_TAG, SPAN_WITH_EMPTY_TAGS_AND_EVENTS);
|
||||
assertThatEventIsValid("unknown_event", SPAN_WITH_EMPTY_TAGS_AND_EVENTS);
|
||||
assertThatEventIsValid(A_BAR_EVENT, SPAN_WITH_EMPTY_TAGS_AND_EVENTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_not_fail_when_keys_and_values_are_properly_prefixed() {
|
||||
assertThatKeyIsValid("some.key", SPAN_WITH_DYNAMIC_ENTRIES);
|
||||
assertThatKeyIsValid(String.format(MyDynamicTags.A_DYNAMIC_TAG.getKey(), "some"), SPAN_WITH_DYNAMIC_ENTRIES);
|
||||
assertThatEventIsValid("some.value", SPAN_WITH_DYNAMIC_ENTRIES);
|
||||
assertThatEventIsValid(String.format(MyDynamicEvents.A_DYNAMIC_EVENT.getValue(), "some"),
|
||||
SPAN_WITH_DYNAMIC_ENTRIES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_not_fail_when_span_was_started_and_then_ended() {
|
||||
assertThatSpanStartedBeforeEnd(
|
||||
new ImmutableAssertingSpan(SPAN_WITH_NOT_MATCHING_PREFIX, BDDMockito.mock(Span.class)).start());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fail_when_assertion_is_on_and_a_key_is_unknown() {
|
||||
thenThrownBy(() -> assertThatKeyIsValid("unknown_key", SPAN_WITH_NOT_MATCHING_PREFIX))
|
||||
.hasMessageContaining("The key [unknown_key] is invalid");
|
||||
thenThrownBy(() -> assertThatKeyIsValid(A_FOO_TAG, SPAN_WITH_NOT_MATCHING_PREFIX))
|
||||
.hasMessageContaining("The key [foo.key] is invalid");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fail_when_assertion_is_on_and_an_event_is_unknown() {
|
||||
thenThrownBy(() -> assertThatEventIsValid("unknown_event", SPAN_WITH_PREFIX))
|
||||
.hasMessageContaining("The event [unknown_event] is invalid");
|
||||
thenThrownBy(() -> assertThatEventIsValid(A_BAR_EVENT, SPAN_WITH_PREFIX))
|
||||
.hasMessageContaining("The event [bar.value] is invalid");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fail_when_assertion_is_on_and_a_key_is_known_but_wrongly_prefixed() {
|
||||
thenThrownBy(() -> assertThatKeyIsValid("bar.key", SPAN_WITH_NOT_MATCHING_PREFIX))
|
||||
.hasMessageContaining("Also it has start with [foo.] prefix");
|
||||
thenThrownBy(() -> assertThatKeyIsValid(MyTagsWithNotMatchingPrefix.A_BAR_TAG, SPAN_WITH_NOT_MATCHING_PREFIX))
|
||||
.hasMessageContaining("Also it has start with [foo.] prefix");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fail_when_assertion_is_on_and_an_event_is_known_but_wrongly_prefixed() {
|
||||
thenThrownBy(() -> assertThatEventIsValid("bar.value", SPAN_WITH_NOT_MATCHING_PREFIX))
|
||||
.hasMessageContaining("Also it has start with [foo.] prefix");
|
||||
thenThrownBy(() -> assertThatEventIsValid(A_BAR_EVENT, SPAN_WITH_NOT_MATCHING_PREFIX))
|
||||
.hasMessageContaining("Also it has start with [foo.] prefix");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fail_when_assertion_is_on_and_a_key_is_known_but_dynamic_key_is_not_matched() {
|
||||
thenThrownBy(() -> assertThatKeyIsValid("notmatching", SPAN_WITH_DYNAMIC_ENTRIES))
|
||||
.hasMessageContaining("The key [notmatching] is invalid. You can use only one matching [%s.key]");
|
||||
thenThrownBy(() -> assertThatKeyIsValid(MySimpleTag.A_SIMPLE_TAG, SPAN_WITH_DYNAMIC_ENTRIES))
|
||||
.hasMessageContaining("The key [simple] is invalid. You can use only one matching [%s.key]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fail_when_assertion_is_on_and_an_event_is_known_but_dynamic_value_is_not_matched() {
|
||||
thenThrownBy(() -> assertThatEventIsValid("notmatching", SPAN_WITH_DYNAMIC_ENTRIES))
|
||||
.hasMessageContaining("The event [notmatching] is invalid. You can use only one matching [%s.value]");
|
||||
thenThrownBy(() -> assertThatEventIsValid(MySimpleEvent.A_SIMPLE_EVENT, SPAN_WITH_DYNAMIC_ENTRIES))
|
||||
.hasMessageContaining("The event [simple] is invalid. You can use only one matching [%s.value]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fail_when_assertion_is_on_and_name_is_invalid() {
|
||||
thenThrownBy(() -> assertThatNameIsValid("unknown_name", SPAN_WITH_NOT_MATCHING_PREFIX))
|
||||
.hasMessageContaining("The name [unknown_name] is invalid");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fail_when_assertion_is_on_and_name_is_not_matching() {
|
||||
thenThrownBy(() -> assertThatNameIsValid("unknown_name", SPAN_WITH_DYNAMIC_ENTRIES)).hasMessageContaining(
|
||||
"The name [unknown_name] is invalid. You can use only one matching [%s somename]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fail_when_span_was_ended_but_not_started() {
|
||||
thenThrownBy(() -> assertThatSpanStartedBeforeEnd(
|
||||
new ImmutableAssertingSpan(SPAN_WITH_NOT_MATCHING_PREFIX, BDDMockito.mock(Span.class))))
|
||||
.hasMessageContaining("The span was not started");
|
||||
}
|
||||
|
||||
enum MySpan implements DocumentedSpan {
|
||||
|
||||
SPAN_WITH_PREFIX {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagKey[] getTagKeys() {
|
||||
return MyTags.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventValue[] getEvents() {
|
||||
return MyEvents.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prefix() {
|
||||
return "foo.";
|
||||
}
|
||||
},
|
||||
|
||||
SPAN_WITH_NOT_MATCHING_PREFIX {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "bar";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagKey[] getTagKeys() {
|
||||
return MyTagsWithNotMatchingPrefix.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventValue[] getEvents() {
|
||||
return MyEventsWithNotMatchingPrefix.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prefix() {
|
||||
return "foo.";
|
||||
}
|
||||
},
|
||||
|
||||
SPAN_WITH_EMPTY_TAGS_AND_EVENTS {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "baz";
|
||||
}
|
||||
},
|
||||
|
||||
SPAN_WITH_DYNAMIC_ENTRIES {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "%s somename";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagKey[] getTagKeys() {
|
||||
return MyDynamicTags.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventValue[] getEvents() {
|
||||
return MyDynamicEvents.values();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum MyTags implements TagKey {
|
||||
|
||||
A_FOO_TAG {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "foo.key";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum MyEvents implements EventValue {
|
||||
|
||||
A_FOO_EVENT {
|
||||
@Override
|
||||
public String getValue() {
|
||||
return "foo.value";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum MyTagsWithNotMatchingPrefix implements TagKey {
|
||||
|
||||
A_BAR_TAG {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "bar.key";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum MyEventsWithNotMatchingPrefix implements EventValue {
|
||||
|
||||
A_BAR_EVENT {
|
||||
@Override
|
||||
public String getValue() {
|
||||
return "bar.value";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum MySimpleTag implements TagKey {
|
||||
|
||||
A_SIMPLE_TAG {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "simple";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum MySimpleEvent implements EventValue {
|
||||
|
||||
A_SIMPLE_EVENT {
|
||||
@Override
|
||||
public String getValue() {
|
||||
return "simple";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum MyDynamicTags implements TagKey {
|
||||
|
||||
A_DYNAMIC_TAG {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "%s.key";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum MyDynamicEvents implements EventValue {
|
||||
|
||||
A_DYNAMIC_EVENT {
|
||||
@Override
|
||||
public String getValue() {
|
||||
return "%s.value";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user