Migrate AWS SDK instrumentation to decorators
MODIFY
dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AWSClientInstrumentation.java
dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AWSClientInstrumentation.java
@@ -8,7 +8,6 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
import com.amazonaws.handlers.RequestHandler2;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
-import io.opentracing.util.GlobalTracer;
import java.util.List;
import java.util.Map;
import net.bytebuddy.asm.Advice;
@@ -36,8 +35,11 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
@Override
public String[] helperClassNames() {
return new String[] {
- "datadog.trace.instrumentation.aws.v0.TracingRequestHandler",
- "datadog.trace.instrumentation.aws.v0.SpanDecorator"
+ "datadog.trace.agent.decorator.BaseDecorator",
+ "datadog.trace.agent.decorator.ClientDecorator",
+ "datadog.trace.agent.decorator.HttpClientDecorator",
+ packageName + ".AwsSdkClientDecorator",
+ packageName + ".TracingRequestHandler",
};
}
@@ -59,7 +61,7 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
}
}
if (!hasDDHandler) {
- handlers.add(new TracingRequestHandler(GlobalTracer.get()));
+ handlers.add(TracingRequestHandler.INSTANCE);
}
}
}

ADD
dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AwsSdkClientDecorator.java
+package datadog.trace.instrumentation.aws.v0;
+
+import com.amazonaws.AmazonWebServiceResponse;
+import com.amazonaws.Request;
+import com.amazonaws.Response;
+import datadog.trace.agent.decorator.HttpClientDecorator;
+import datadog.trace.api.DDTags;
+import io.opentracing.Span;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+class AwsSdkClientDecorator extends HttpClientDecorator<Request, Response> {
+ public static final AwsSdkClientDecorator INSTANCE = new AwsSdkClientDecorator();
+
+ static final String COMPONENT_NAME = "java-aws-sdk";
+
+ private final Map<String, String> serviceNames = new ConcurrentHashMap<>();
+ private final Map<Class, String> operationNames = new ConcurrentHashMap<>();
+
+ @Override
+ public Span onRequest(final Span span, final Request request) {
+ // Call super first because we override the resource name below.
+ super.onRequest(span, request);
+
+ final String awsServiceName = request.getServiceName();
+ final Class<?> awsOperation = request.getOriginalRequest().getClass();
+
+ span.setTag("aws.agent", COMPONENT_NAME);
+ span.setTag("aws.service", awsServiceName);
+ span.setTag("aws.operation", awsOperation.getSimpleName());
+ span.setTag("aws.endpoint", request.getEndpoint().toString());
+
+ span.setTag(
+ DDTags.RESOURCE_NAME,
+ remapServiceName(awsServiceName) + "." + remapOperationName(awsOperation));
+
+ return span;
+ }
+
+ @Override
+ public Span onResponse(final Span span, final Response response) {
+ if (response.getAwsResponse() instanceof AmazonWebServiceResponse) {
+ final AmazonWebServiceResponse awsResp = (AmazonWebServiceResponse) response.getAwsResponse();
+ span.setTag("aws.requestId", awsResp.getRequestId());
+ }
+ return super.onResponse(span, response);
+ }
+
+ private String remapServiceName(final String serviceName) {
+ if (!serviceNames.containsKey(serviceName)) {
+ serviceNames.put(serviceName, serviceName.replace("Amazon", "").trim());
+ }
+ return serviceNames.get(serviceName);
+ }
+
+ private String remapOperationName(final Class<?> awsOperation) {
+ if (!operationNames.containsKey(awsOperation)) {
+ operationNames.put(awsOperation, awsOperation.getSimpleName().replace("Request", ""));
+ }
+ return operationNames.get(awsOperation);
+ }
+
+ @Override
+ protected String service() {
+ return COMPONENT_NAME;
+ }
+
+ @Override
+ protected String[] instrumentationNames() {
+ return new String[] {"aws-sdk"};
+ }
+
+ @Override
+ protected String component() {
+ return COMPONENT_NAME;
+ }
+
+ @Override
+ protected String method(final Request request) {
+ return request.getHttpMethod().name();
+ }
+
+ @Override
+ protected String url(final Request request) {
+ return request.getEndpoint().toString();
+ }
+
+ @Override
+ protected String hostname(final Request request) {
+ return null;
+ }
+
+ @Override
+ protected Integer port(final Request request) {
+ return null;
+ }
+
+ @Override
+ protected Integer status(final Response response) {
+ return response.getHttpResponse().getStatusCode();
+ }
+}

DELETE
dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/SpanDecorator.java
-/*
- * Copyright 2017-2018 The OpenTracing 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
- *
- * 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 datadog.trace.instrumentation.aws.v0;
-
-import static io.opentracing.log.Fields.ERROR_OBJECT;
-
-import com.amazonaws.AmazonWebServiceResponse;
-import com.amazonaws.Request;
-import com.amazonaws.Response;
-import datadog.trace.api.DDSpanTypes;
-import datadog.trace.api.DDTags;
-import io.opentracing.Span;
-import io.opentracing.tag.Tags;
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-class SpanDecorator {
- static final String COMPONENT_NAME = "java-aws-sdk";
-
- private static final Map<String, String> SERVICE_NAMES = new ConcurrentHashMap<>();
- private static final Map<Class, String> OPERATION_NAMES = new ConcurrentHashMap<>();
-
- static void onRequest(final Request<?> request, final Span span) {
- Tags.COMPONENT.set(span, COMPONENT_NAME);
- Tags.HTTP_METHOD.set(span, request.getHttpMethod().name());
- Tags.HTTP_URL.set(span, request.getEndpoint().toString());
-
- final String awsServiceName = request.getServiceName();
- final Class<?> awsOperation = request.getOriginalRequest().getClass();
-
- span.setTag("aws.agent", COMPONENT_NAME);
- span.setTag("aws.service", awsServiceName);
- span.setTag("aws.operation", awsOperation.getSimpleName());
- span.setTag("aws.endpoint", request.getEndpoint().toString());
-
- span.setTag(DDTags.SERVICE_NAME, COMPONENT_NAME);
- span.setTag(
- DDTags.RESOURCE_NAME,
- remapServiceName(awsServiceName) + "." + remapOperationName(awsOperation));
- span.setTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_CLIENT);
- }
-
- static void onResponse(final Response response, final Span span) {
- Tags.HTTP_STATUS.set(span, response.getHttpResponse().getStatusCode());
- if (response.getAwsResponse() instanceof AmazonWebServiceResponse) {
- final AmazonWebServiceResponse awsResp = (AmazonWebServiceResponse) response.getAwsResponse();
- span.setTag("aws.requestId", awsResp.getRequestId());
- }
- }
-
- static void onError(final Throwable throwable, final Span span) {
- Tags.ERROR.set(span, Boolean.TRUE);
- span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
- }
-
- private static String remapServiceName(final String serviceName) {
- if (!SERVICE_NAMES.containsKey(serviceName)) {
- SERVICE_NAMES.put(serviceName, serviceName.replace("Amazon", "").trim());
- }
- return SERVICE_NAMES.get(serviceName);
- }
-
- private static String remapOperationName(final Class<?> awsOperation) {
- if (!OPERATION_NAMES.containsKey(awsOperation)) {
- OPERATION_NAMES.put(awsOperation, awsOperation.getSimpleName().replace("Request", ""));
- }
- return OPERATION_NAMES.get(awsOperation);
- }
-}

MODIFY
dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/TracingRequestHandler.java
dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/TracingRequestHandler.java
@@ -19,39 +19,19 @@ import com.amazonaws.Response;
import com.amazonaws.handlers.HandlerContextKey;
import com.amazonaws.handlers.RequestHandler2;
import io.opentracing.Scope;
-import io.opentracing.SpanContext;
-import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMapInjectAdapter;
-import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
/** Tracing Request Handler */
public class TracingRequestHandler extends RequestHandler2 {
+ public static TracingRequestHandler INSTANCE = new TracingRequestHandler();
// Note: aws1.x sdk doesn't have any truly async clients so we can store scope in request context
// safely.
private static final HandlerContextKey<Scope> SCOPE_CONTEXT_KEY =
new HandlerContextKey<>("DatadogScope");
- private final SpanContext parentContext; // for Async Client
- private final Tracer tracer;
-
- public TracingRequestHandler(final Tracer tracer) {
- parentContext = null;
- this.tracer = tracer;
- }
-
- /**
- * In case of Async Client: beforeRequest runs in separate thread therefore we need to inject
- * parent context to build chain
- *
- * @param parentContext parent context
- */
- public TracingRequestHandler(final SpanContext parentContext, final Tracer tracer) {
- this.parentContext = parentContext;
- this.tracer = tracer;
- }
-
@Override
public AmazonWebServiceRequest beforeMarshalling(final AmazonWebServiceRequest request) {
return request;
@@ -60,20 +40,14 @@ public class TracingRequestHandler extends RequestHandler2 {
/** {@inheritDoc} */
@Override
public void beforeRequest(final Request<?> request) {
- // Note: not setting Component tag here because it is always set by SpanDecorator
- final Tracer.SpanBuilder spanBuilder =
- tracer.buildSpan("aws.command").withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT);
-
- if (parentContext != null) {
- spanBuilder.asChildOf(parentContext);
- }
-
- final Scope scope = spanBuilder.startActive(true);
- SpanDecorator.onRequest(request, scope.span());
+ final Scope scope = GlobalTracer.get().buildSpan("aws.command").startActive(true);
+ AwsSdkClientDecorator.INSTANCE.afterStart(scope.span());
+ AwsSdkClientDecorator.INSTANCE.onRequest(scope.span(), request);
// We inject headers at aws-client level because aws requests may be signed and adding headers
// on http-client level may break signature.
- tracer.inject(
+ GlobalTracer.get()
+ .inject(
scope.span().context(),
Format.Builtin.HTTP_HEADERS,
new TextMapInjectAdapter(request.getHeaders()));
@@ -85,7 +59,8 @@ public class TracingRequestHandler extends RequestHandler2 {
@Override
public void afterResponse(final Request<?> request, final Response<?> response) {
final Scope scope = request.getHandlerContext(SCOPE_CONTEXT_KEY);
- SpanDecorator.onResponse(response, scope.span());
+ AwsSdkClientDecorator.INSTANCE.onResponse(scope.span(), response);
+ AwsSdkClientDecorator.INSTANCE.beforeFinish(scope.span());
scope.close();
}
@@ -93,7 +68,8 @@ public class TracingRequestHandler extends RequestHandler2 {
@Override
public void afterError(final Request<?> request, final Response<?> response, final Exception e) {
final Scope scope = request.getHandlerContext(SCOPE_CONTEXT_KEY);
- SpanDecorator.onError(e, scope.span());
+ AwsSdkClientDecorator.INSTANCE.onError(scope.span(), e);
+ AwsSdkClientDecorator.INSTANCE.beforeFinish(scope.span());
scope.close();
}
}

MODIFY
dd-java-agent/instrumentation/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AbstractAwsClientInstrumentation.java
dd-java-agent/instrumentation/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AbstractAwsClientInstrumentation.java
@@ -12,9 +12,12 @@ public abstract class AbstractAwsClientInstrumentation extends Instrumenter.Defa
@Override
public String[] helperClassNames() {
return new String[] {
- AwsClientInstrumentation.class.getPackage().getName() + ".TracingExecutionInterceptor",
- AwsClientInstrumentation.class.getPackage().getName()
- + ".TracingExecutionInterceptor$InjectAdapter"
+ "datadog.trace.agent.decorator.BaseDecorator",
+ "datadog.trace.agent.decorator.ClientDecorator",
+ "datadog.trace.agent.decorator.HttpClientDecorator",
+ packageName + ".AwsSdkClientDecorator",
+ packageName + ".TracingExecutionInterceptor",
+ packageName + ".TracingExecutionInterceptor$InjectAdapter"
};
}
}

ADD
dd-java-agent/instrumentation/aws-java-sdk-2.2/src/main/java8/datadog/trace/instrumentation/aws/v2/AwsSdkClientDecorator.java
+package datadog.trace.instrumentation.aws.v2;
+
+import datadog.trace.agent.decorator.HttpClientDecorator;
+import datadog.trace.api.DDTags;
+import io.opentracing.Span;
+import software.amazon.awssdk.awscore.AwsResponse;
+import software.amazon.awssdk.core.SdkResponse;
+import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
+import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
+import software.amazon.awssdk.http.SdkHttpRequest;
+import software.amazon.awssdk.http.SdkHttpResponse;
+
+class AwsSdkClientDecorator extends HttpClientDecorator<SdkHttpRequest, SdkHttpResponse> {
+ public static final AwsSdkClientDecorator INSTANCE = new AwsSdkClientDecorator();
+
+ static final String COMPONENT_NAME = "java-aws-sdk";
+
+ public Span onAttributes(final Span span, final ExecutionAttributes attributes) {
+
+ final String awsServiceName = attributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
+ final String awsOperation = attributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
+
+ // Resource Name has to be set after the HTTP_URL because otherwise decorators overwrite it
+ span.setTag(DDTags.RESOURCE_NAME, awsServiceName + "." + awsOperation);
+
+ span.setTag("aws.agent", COMPONENT_NAME);
+ span.setTag("aws.service", awsServiceName);
+ span.setTag("aws.operation", awsOperation);
+
+ return span;
+ }
+
+ // Not overriding the super. Should call both with each type of response.
+ public Span onResponse(final Span span, final SdkResponse response) {
+ if (response instanceof AwsResponse) {
+ span.setTag("aws.requestId", ((AwsResponse) response).responseMetadata().requestId());
+ }
+ return span;
+ }
+
+ @Override
+ protected String service() {
+ return COMPONENT_NAME;
+ }
+
+ @Override
+ protected String[] instrumentationNames() {
+ return new String[] {"aws-sdk"};
+ }
+
+ @Override
+ protected String component() {
+ return COMPONENT_NAME;
+ }
+
+ @Override
+ protected String method(final SdkHttpRequest request) {
+ return request.method().name();
+ }
+
+ @Override
+ protected String url(final SdkHttpRequest request) {
+ return request.protocol()
+ + "://"
+ + request.host()
+ + ":"
+ + request.port()
+ + request.encodedPath();
+ }
+
+ @Override
+ protected String hostname(final SdkHttpRequest request) {
+ return request.host();
+ }
+
+ @Override
+ protected Integer port(final SdkHttpRequest request) {
+ return request.port();
+ }
+
+ @Override
+ protected Integer status(final SdkHttpResponse response) {
+ return response.statusCode();
+ }
+}

MODIFY
dd-java-agent/instrumentation/aws-java-sdk-2.2/src/main/java8/datadog/trace/instrumentation/aws/v2/TracingExecutionInterceptor.java
dd-java-agent/instrumentation/aws-java-sdk-2.2/src/main/java8/datadog/trace/instrumentation/aws/v2/TracingExecutionInterceptor.java
*/
package datadog.trace.instrumentation.aws.v2;
-import static io.opentracing.log.Fields.ERROR_OBJECT;
-
-import datadog.trace.api.DDSpanTypes;
-import datadog.trace.api.DDTags;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
-import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Consumer;
-import software.amazon.awssdk.awscore.AwsResponse;
-import software.amazon.awssdk.core.SdkResponse;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.interceptor.Context;
import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
-import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
import software.amazon.awssdk.http.SdkHttpRequest;
/** AWS request execution interceptor */
public class TracingExecutionInterceptor implements ExecutionInterceptor {
- private static final TracingExecutionInterceptor INSTANCE = new TracingExecutionInterceptor();
// Note: it looks like this lambda doesn't get generated as a separate class file so we do not
// need to inject helper for it.
private static final Consumer<ClientOverrideConfiguration.Builder>
- OVERRIDE_CONFIGURATION_CONSUMER = builder -> builder.addExecutionInterceptor(INSTANCE);
-
- static final String COMPONENT_NAME = "java-aws-sdk";
+ OVERRIDE_CONFIGURATION_CONSUMER =
+ builder -> builder.addExecutionInterceptor(new TracingExecutionInterceptor());
private static final ExecutionAttribute<Span> SPAN_ATTRIBUTE =
new ExecutionAttribute<>("DatadogSpan");
@@ -58,16 +45,9 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
@Override
public void beforeExecution(
final Context.BeforeExecution context, final ExecutionAttributes executionAttributes) {
- final Tracer tracer = GlobalTracer.get();
-
- final Tracer.SpanBuilder builder =
- tracer
- .buildSpan("aws.command")
- .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
- .withTag(Tags.COMPONENT.getKey(), COMPONENT_NAME)
- .withTag(DDTags.SERVICE_NAME, COMPONENT_NAME)
- .withTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_CLIENT);
- executionAttributes.putAttribute(SPAN_ATTRIBUTE, builder.start());
+ final Span span = GlobalTracer.get().buildSpan("aws.command").start();
+ AwsSdkClientDecorator.INSTANCE.afterStart(span);
+ executionAttributes.putAttribute(SPAN_ATTRIBUTE, span);
}
@Override
@@ -76,35 +56,8 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
final SdkHttpRequest httpRequest = context.httpRequest();
- Tags.HTTP_METHOD.set(span, httpRequest.method().name());
-
- try {
- final URI requestUri = httpRequest.getUri();
- final String uri =
- new URI(
- requestUri.getScheme(),
- null,
- requestUri.getHost(),
- requestUri.getPort(),
- requestUri.getPath(),
- null,
- null)
- .toString();
- Tags.HTTP_URL.set(span, uri);
- } catch (final URISyntaxException e) {
- Tags.HTTP_URL.set(span, "failed-to-parse");
- }
- final String awsServiceName =
- executionAttributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
- final String awsOperation =
- executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
-
- // Resource Name has to be set after the HTTP_URL because otherwise decorators overwrite it
- span.setTag(DDTags.RESOURCE_NAME, awsServiceName + "." + awsOperation);
-
- span.setTag("aws.agent", COMPONENT_NAME);
- span.setTag("aws.service", awsServiceName);
- span.setTag("aws.operation", awsOperation);
+ AwsSdkClientDecorator.INSTANCE.onRequest(span, httpRequest);
+ AwsSdkClientDecorator.INSTANCE.onAttributes(span, executionAttributes);
}
@Override
@@ -132,23 +85,18 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
public void afterExecution(
final Context.AfterExecution context, final ExecutionAttributes executionAttributes) {
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
- try {
- Tags.HTTP_STATUS.set(span, context.httpResponse().statusCode());
- final SdkResponse response = context.response();
- if (response instanceof AwsResponse) {
- span.setTag("aws.requestId", ((AwsResponse) response).responseMetadata().requestId());
- }
- } finally {
+ // Call onResponse on both types of responses:
+ AwsSdkClientDecorator.INSTANCE.onResponse(span, context.response());
+ AwsSdkClientDecorator.INSTANCE.onResponse(span, context.httpResponse());
+ AwsSdkClientDecorator.INSTANCE.beforeFinish(span);
span.finish();
}
- }
@Override
public void onExecutionFailure(
final Context.FailedExecution context, final ExecutionAttributes executionAttributes) {
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
- Tags.ERROR.set(span, Boolean.TRUE);
- span.log(Collections.singletonMap(ERROR_OBJECT, context.exception()));
+ AwsSdkClientDecorator.INSTANCE.onError(span, context.exception());
}
public static Consumer<ClientOverrideConfiguration.Builder> getOverrideConfigurationConsumer() {

MODIFY
dd-java-agent/instrumentation/aws-java-sdk-2.2/src/test/groovy/AwsClientTest.groovy
dd-java-agent/instrumentation/aws-java-sdk-2.2/src/test/groovy/AwsClientTest.groovy
@@ -76,6 +76,8 @@ class AwsClientTest extends AgentTestRunner {
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" expectedUrl
"$Tags.HTTP_METHOD.key" "$method"
+ "$Tags.PEER_HOSTNAME.key" "localhost"
+ "$Tags.PEER_PORT.key" server.address.port
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
"aws.service" "$service"
@@ -167,6 +169,8 @@ class AwsClientTest extends AgentTestRunner {
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" expectedUrl
"$Tags.HTTP_METHOD.key" "$method"
+ "$Tags.PEER_HOSTNAME.key" "localhost"
+ "$Tags.PEER_PORT.key" server.address.port
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
"aws.service" "$service"

Migrate AWS SDK instrumentation to decorators