Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
162ffdc
feature(GoodLoggingPractice): init draft but not error produced
Apr 12, 2019
7dbcf10
resolve conflict
Apr 12, 2019
609940a
fix(space): no extra empty space
Apr 12, 2019
5f6f36e
feat(fix): refactor based on Connie' code change request
Apr 16, 2019
408a372
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-java …
Apr 16, 2019
c342be4
feat(import): remove unused import lib
Apr 16, 2019
d6704ee
Merge branch 'master' into CheckStyleRule-GoodLoggingPractice
mssfang Apr 16, 2019
fcdc012
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-java …
Apr 18, 2019
cf277b4
feat(update): corrected and tested
Apr 23, 2019
b5c0225
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-java …
Apr 23, 2019
e2531ff
Merge branch 'CheckStyleRule-GoodLoggingPractice' of https://github.c…
Apr 23, 2019
ebdcb13
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-java …
May 8, 2019
77225db
while loop works
May 10, 2019
32793cd
working solution for guard blocked isSomethingEnabled
May 13, 2019
36390d8
resolve stash pop conflict
May 14, 2019
66c99db
fix: refactor all based on the new unstanding
May 14, 2019
95a00bd
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-java …
May 16, 2019
8479eea
resolve conflict since May
Aug 1, 2019
6b1c866
refactor and fix bugs
Aug 2, 2019
694370c
resolve conflict
Aug 19, 2019
c2fa02f
based on new feedback and fixes
Aug 19, 2019
1187938
fixes errors on Identity service
Aug 19, 2019
55bbbf9
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-java …
Aug 19, 2019
caa685e
resolve conflict
Aug 19, 2019
239f621
fixes based on Srekanta's feedback
Aug 19, 2019
9781106
resolve conflict
Aug 19, 2019
2fb3108
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-java …
Aug 20, 2019
06cf258
fixes based on feedback
Aug 20, 2019
95e4545
resolve conflict
Aug 20, 2019
87d45ed
resolve conflict
Aug 20, 2019
adcb2a9
add java.util.logging
Aug 21, 2019
871e182
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-java …
Aug 23, 2019
6c449c3
updates docs and minor changes
Aug 25, 2019
f5c4090
no throw change
Aug 25, 2019
b43780f
refactor
Aug 25, 2019
10af7b6
resolve conflict
Aug 26, 2019
bd73a70
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-java …
Aug 26, 2019
8adfc66
resolve conflict
Aug 27, 2019
e7a3dc5
Update eng/code-quality-reports/src/main/java/com/azure/tools/checkst…
mssfang Aug 27, 2019
98a3227
re-wording
Aug 27, 2019
044c19c
Merge branch 'CheckStyleRule-GoodLoggingPractice' of https://github.c…
Aug 27, 2019
9937e0d
Update eng/code-quality-reports/src/main/java/com/azure/tools/checkst…
mssfang Aug 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.tools.checkstyle.checks;

import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.FullIdent;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
* Good Logging Practice:
* <ol>
* <li>Non-static Logger instance.</li>
* <li>Logger variable name should be 'logger' in public class and not exist in an implementation package.</li>
Comment thread
conniey marked this conversation as resolved.
Outdated
* <li>Should not use any external logger class, only use ClientLogger. No slf4j, log4j, or other logging imports are allowed.</li>
* <li>'System.out' and 'System.err' is not allowed as well.</li>
* </ol>
*/
public class GoodLoggingCheck extends AbstractCheck {
private static final String CLIENT_LOGGER_PATH = "com.azure.core.util.logging.ClientLogger";
private static final String CLIENT_LOGGER = "ClientLogger";
private static final String LOGGER_NAME_ERROR = "ClientLogger instance naming: use ''%s'' instead of ''%s'' for consistency.";
private static final String STATIC_LOGGER_ERROR = "Reference to ClientLogger should not be static: remove static modifier.";
Comment thread
mssfang marked this conversation as resolved.
Outdated
private static final String NOT_CLIENT_LOGGER_ERROR = "Do not use %s class. Use ''%s'' as a logging mechanism instead of ''%s''.";
private static final String LOGGER = "logger";

private boolean hasClientLoggerImported;
private String className;

private static final Set<String> INVALID_LOG_SET = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"org.slf4j", "org.apache.logging.log4j"
Comment thread
mssfang marked this conversation as resolved.
Outdated
)));

@Override
public int[] getDefaultTokens() {
return getRequiredTokens();
}

@Override
public int[] getAcceptableTokens() {
return getRequiredTokens();
}

@Override
public int[] getRequiredTokens() {
return new int[] {
TokenTypes.IMPORT,
TokenTypes.LITERAL_CLASS,
TokenTypes.LITERAL_NEW,
TokenTypes.VARIABLE_DEF,
TokenTypes.METHOD_CALL
};
}

@Override
public void finishTree(DetailAST ast) {
hasClientLoggerImported = false;
}

@Override
public void visitToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.IMPORT:
String importClassPath = FullIdent.createFullIdentBelow(ast).getText();
hasClientLoggerImported = hasClientLoggerImported || importClassPath.equals(CLIENT_LOGGER_PATH);
for (final String logger : INVALID_LOG_SET) {
if (importClassPath.startsWith(logger)) {
// Checks no use any external logger class.
log(ast, String.format(NOT_CLIENT_LOGGER_ERROR, "external logger", CLIENT_LOGGER_PATH, logger));
}
}
break;
case TokenTypes.LITERAL_CLASS:
if (ast.getNextSibling() == null) {
break;
}
className = ast.getNextSibling().getText();
break;
case TokenTypes.LITERAL_NEW:
checkLoggerInstantiation(ast);
break;
case TokenTypes.VARIABLE_DEF:
checkLoggerNameMatch(ast);
break;
case TokenTypes.METHOD_CALL:
final DetailAST dotToken = ast.findFirstToken(TokenTypes.DOT);
if (dotToken == null) {
return;
}
final String methodCallName = FullIdent.createFullIdentBelow(dotToken).getText();
if (methodCallName.startsWith("System.out") || methodCallName.startsWith("System.err")) {
log(ast, String.format(NOT_CLIENT_LOGGER_ERROR, "Java System", CLIENT_LOGGER_PATH, methodCallName));
}
break;
default:
// Checkstyle complains if there's no default block in switch
break;
}
}

/**
* check if the VARIABLE_DEF AST node type is 'ClientLogger'.
* @param varDefAST VARIABLE_DEF AST node
* @return true if the variable type is 'ClientLogger'.
*/
private boolean isTypeClientLogger(DetailAST varDefAST) {
DetailAST typeAST = varDefAST.findFirstToken(TokenTypes.TYPE);
if (typeAST == null) {
return false;
}
return TokenUtil.findFirstTokenByPredicate(typeAST, node ->
node.getType() == TokenTypes.IDENT && node.getText().equals(CLIENT_LOGGER)
).isPresent();
}

/**
* Check if instantiating a matched class name for the same class.
*
* @param literalNewToken LITERAL_NEW node
*/
private void checkLoggerInstantiation(DetailAST literalNewToken) {
final DetailAST identToken = literalNewToken.findFirstToken(TokenTypes.IDENT);
// Not named 'logger'
if (identToken == null || !identToken.getText().equals(CLIENT_LOGGER)) {
return;
}
// Edge cases
final DetailAST elistToken = literalNewToken.findFirstToken(TokenTypes.ELIST);
final DetailAST exprToken = elistToken.findFirstToken(TokenTypes.EXPR);
if (exprToken.getFirstChild().getType() != TokenTypes.DOT) {
return;
}
// Check instantiation of ClientLogger
final String containerClassName = FullIdent.createFullIdentBelow(exprToken).getText();
// Add suffix of '.class' at the end ot class name
if (!containerClassName.equals(className + ".class")) {
log(literalNewToken, String.format("Not newing a ClientLogger with matching class name. Use ''%s.class'' instead of ''%s.class''", className, containerClassName));
}
}

/**
* Check if the given ClientLogger named 'logger'
*
* @param varToken VARIABLE_DEF node
*/
private void checkLoggerNameMatch(DetailAST varToken) {
if (!hasClientLoggerImported || !isTypeClientLogger(varToken)) {
return;
}
// Check if the Logger instance named as 'logger'.
final DetailAST identAST = varToken.findFirstToken(TokenTypes.IDENT);
if (identAST != null && !identAST.getText().equals(LOGGER)) {
log(varToken, String.format(LOGGER_NAME_ERROR, LOGGER, identAST.getText()));
}
// Check if the Logger is static instance, log as error if it is static instance logger.
if (TokenUtil.findFirstTokenByPredicate(varToken, node -> node.getType() == TokenTypes.MODIFIERS && node.branchContains(TokenTypes.LITERAL_STATIC)).isPresent()) {
log(varToken, STATIC_LOGGER_ERROR);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
<suppress checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" files=".*\BlobOutputStream.java"/>

<!-- Don't apply custom Checkstyle rules to files under test package -->
<suppress checks="com\.azure\.tools\.checkstyle\.checks\.(ExternalDependencyExposedCheck|NoImplInPublicAPI|ServiceClientInstantiationCheck|ServiceClientBuilderCheck|ServiceInterfaceCheck|HttpPipelinePolicyCheck|JavaDocFormatting|JavadocThrowsChecks|EnforceFinalFieldsCheck|ThrowFromClientLoggerCheck)"
<suppress checks="com\.azure\.tools\.checkstyle\.checks\.(ExternalDependencyExposedCheck|NoImplInPublicAPI|ServiceClientInstantiationCheck|ServiceClientBuilderCheck|ServiceInterfaceCheck|HttpPipelinePolicyCheck|JavaDocFormatting|JavadocThrowsChecks|EnforceFinalFieldsCheck|ThrowFromClientLoggerCheck|GoodLoggingCheck)"
files=".*[/\\]src[/\\]test[/\\]java[/\\].*\.java"/>

<!-- Don't apply custom Checkstyle rules to files under implementation package -->
Expand Down Expand Up @@ -130,4 +130,11 @@
<suppress checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" files="com.azure.storage.blob.BlobOutputStream.java"/>
<suppress checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" files="com.azure.storage.blob.BlockBlobClient.java"/>
<suppress checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" files="com.azure.storage.blob.PageBlobAsyncClient.java"/>

<!-- ClientLogger class suppression -->
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files="ClientLogger.java"/>
<!-- Azure Core Test -->
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files=".*[/\\]core[/\\]test[/\\].*\.java"/>
Comment thread
mssfang marked this conversation as resolved.
<!-- Public API already released -->
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files="CertificateUtil.java"/>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious why we added this suppression? It is in an implementation package, and the logger itself is private. 🤔
https://github.com/Azure/azure-sdk-for-java/blob/master/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/util/CertificateUtil.java

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to use logger in static method, the logger has to be static. Not a problem of private, the methods of the classes are public static methods. Can't change these method.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the API is public, but we could remove the logger and it wouldn't break this API. So I was wondering about the "Public API already release" reason... since it doesn't match up.

</suppressions>
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ page at http://checkstyle.sourceforge.net/config.html -->
<!-- CUSTOM CHECKS -->
<!-- Verify the whenever a field is assigned just once in constructor to be final -->
<module name="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck"/>

<!-- Javadoc format: 'param / return / throws' descriptions text should only have one space character after the
parameter name or return -->
<module name="com.azure.tools.checkstyle.checks.JavaDocFormatting"/>
Expand All @@ -336,5 +336,12 @@ page at http://checkstyle.sourceforge.net/config.html -->
1) Must be a public class.
2) Not in an implementation package or sub-package. -->
<module name="com.azure.tools.checkstyle.checks.HttpPipelinePolicyCheck"/>

<!--CUSTOM CHECKS-->
<!-- Good Logging Practice
1) ServiceLogger exists in public API should named 'logger'
2) Non-static ServiceLogger instance logger
3) All classes should use ServiceLogger as logger only but except ServiceLogger itself -->
Comment thread
mssfang marked this conversation as resolved.
Outdated
<module name="com.azure.tools.checkstyle.checks.GoodLoggingCheck"/>
</module>
</module>
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import com.azure.core.http.ProxyOptions;
import com.azure.core.test.models.NetworkCallRecord;
import com.azure.core.test.models.RecordedData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.azure.core.util.logging.ClientLogger;
import reactor.core.publisher.Mono;

import java.net.URI;
Expand All @@ -25,7 +24,7 @@
* HTTP client that plays back {@link NetworkCallRecord NetworkCallRecords}.
*/
public final class PlaybackClient implements HttpClient {
private final Logger logger = LoggerFactory.getLogger(PlaybackClient.class);
private final ClientLogger logger = new ClientLogger(PlaybackClient.class);
private final AtomicInteger count = new AtomicInteger(0);
private final Map<String, String> textReplacementRules;
private final RecordedData recordedData;
Expand Down Expand Up @@ -88,10 +87,8 @@ private Mono<HttpResponse> playbackHttpResponse(final HttpRequest request) {
count.incrementAndGet();

if (networkCallRecord == null) {
if (logger.isWarnEnabled()) {
logger.warn("NOT FOUND - Method: {} URL: {}", incomingMethod, incomingUrl);
logger.warn("Records requested: {}.", count);
}
logger.warning("NOT FOUND - Method: {} URL: {}", incomingMethod, incomingUrl);
logger.warning("Records requested: {}.", count);

return Mono.error(new IllegalStateException("==> Unexpected request: " + incomingMethod + " " + incomingUrl));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import com.azure.core.http.HttpPipelineNextPolicy;
import com.azure.core.http.HttpResponse;
import com.azure.core.implementation.http.UrlBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.azure.core.util.logging.ClientLogger;
import reactor.core.publisher.Mono;

import java.net.MalformedURLException;
Expand All @@ -18,7 +18,7 @@
*/
public class HostPolicy implements HttpPipelinePolicy {
private final String host;
private static final Logger LOGGER = LoggerFactory.getLogger(HostPolicy.class);
private final ClientLogger logger = new ClientLogger(HostPolicy.class);

/**
* Create HostPolicy.
Expand All @@ -31,7 +31,7 @@ public HostPolicy(String host) {

@Override
public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
LOGGER.info("Setting host to {}", host);
logger.info("Setting host to {}", host);

Mono<HttpResponse> result;
final UrlBuilder urlBuilder = UrlBuilder.parse(context.httpRequest().url());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import com.azure.core.http.HttpPipelineNextPolicy;
import com.azure.core.http.HttpResponse;
import com.azure.core.implementation.http.UrlBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.azure.core.util.logging.ClientLogger;
import reactor.core.publisher.Mono;

import java.net.MalformedURLException;
Expand All @@ -19,7 +18,7 @@
public class PortPolicy implements HttpPipelinePolicy {
private final int port;
private final boolean overwrite;
private static final Logger LOGGER = LoggerFactory.getLogger(PortPolicy.class);
private final ClientLogger logger = new ClientLogger(PortPolicy.class);

/**
* Create a new PortPolicy object.
Expand All @@ -36,7 +35,7 @@ public PortPolicy(int port, boolean overwrite) {
public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
final UrlBuilder urlBuilder = UrlBuilder.parse(context.httpRequest().url());
if (overwrite || urlBuilder.port() == null) {
LOGGER.info("Changing port to {}", port);
logger.info("Changing port to {}", port);

try {
context.httpRequest().url(urlBuilder.port(port).toURL());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import com.azure.core.http.HttpPipelineNextPolicy;
import com.azure.core.http.HttpResponse;
import com.azure.core.implementation.http.UrlBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.azure.core.util.logging.ClientLogger;
import reactor.core.publisher.Mono;

import java.net.MalformedURLException;
Expand All @@ -19,7 +18,7 @@
public class ProtocolPolicy implements HttpPipelinePolicy {
private final String protocol;
private final boolean overwrite;
private static final Logger LOGGER = LoggerFactory.getLogger(ProtocolPolicy.class);
private final ClientLogger logger = new ClientLogger(ProtocolPolicy.class);

/**
* Create a new ProtocolPolicy.
Expand All @@ -36,7 +35,7 @@ public ProtocolPolicy(String protocol, boolean overwrite) {
public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
final UrlBuilder urlBuilder = UrlBuilder.parse(context.httpRequest().url());
if (overwrite || urlBuilder.scheme() == null) {
LOGGER.info("Setting protocol to {}", protocol);
logger.info("Setting protocol to {}", protocol);

try {
context.httpRequest().url(urlBuilder.scheme(protocol).toURL());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ private static Long parseDateToEpochSeconds(String dateTime) {
try {
return Long.parseLong(dateTime);
} catch (NumberFormatException e) {
System.err.println(e.getMessage());
logger.error(e.getMessage());
}

try {
return Instant.from(dtf.parse(dateTime)).toEpochMilli() / 1000L;
} catch (DateTimeParseException e) {
System.err.println(e.getMessage());
logger.error(e.getMessage());
}

throw logger.logExceptionAsError(new IllegalArgumentException("Unable to parse date time " + dateTime));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public QueueServiceAsyncClient buildAsyncClient() {
Objects.requireNonNull(endpoint);

if (sasTokenCredential == null && sharedKeyCredential == null && bearerTokenCredential == null) {
logger.logExceptionAsError(new IllegalArgumentException("Credentials are required for authorization"));
throw logger.logExceptionAsError(new IllegalArgumentException("Credentials are required for authorization"));
}

if (pipeline != null) {
Expand Down Expand Up @@ -198,7 +198,7 @@ public QueueServiceClientBuilder endpoint(String endpoint) {
this.bearerTokenCredential = null;
}
} catch (MalformedURLException ex) {
logger.logExceptionAsError(new IllegalArgumentException("The Azure Storage Queue endpoint url is malformed."));
throw logger.logExceptionAsError(new IllegalArgumentException("The Azure Storage Queue endpoint url is malformed."));
}

return this;
Expand Down