Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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,181 @@
// 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.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;

/**
* Good Logging Practice:
* <ol>
* <li>A non-static instance logger.</li>
* <li>ClientLogger in public API should all named 'logger', public API classes are those classes that are declared
* as public and that do not exist in an implementation package or subpackage.</li>
* <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 = "logger";

private static final String LOGGER_NAME_ERROR = "ClientLogger instance naming: use ''%s'' instead of ''%s'' for consistency.";
private static final String STATIC_LOGGER_ERROR = "ClientLogger should not be static. Remove static modifier.";
private static final String NOT_CLIENT_LOGGER_ERROR = "Do not use %s class. Use ''%s'' as a logging mechanism instead of ''%s''.";

// Boolean indicator that indicates if the java class imports ClientLogger
private boolean hasClientLoggerImported;
// A container stores the class names, pop top element if exist the class name AST node
private Deque<String> classNameDeque = new ArrayDeque<>();
// Collection of Invalid logging packages
private static final Set<String> INVALID_LOGS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"org.slf4j", "org.apache.logging.log4j", "java.util.logging"
)));

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

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

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

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

@Override
public void leaveToken(DetailAST ast) {
if (ast.getType() == TokenTypes.CLASS_DEF) {
classNameDeque.pollFirst();
}
}

@Override
public void visitToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.IMPORT:
final String importClassPath = FullIdent.createFullIdentBelow(ast).getText();
hasClientLoggerImported = hasClientLoggerImported || importClassPath.equals(CLIENT_LOGGER_PATH);

INVALID_LOGS.forEach(item -> {
if (importClassPath.startsWith(item)) {
log(ast, String.format(NOT_CLIENT_LOGGER_ERROR, "external logger", CLIENT_LOGGER_PATH, item));
}
});
break;
case TokenTypes.CLASS_DEF:
classNameDeque.addFirst(ast.findFirstToken(TokenTypes.IDENT).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) {
final 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 ClientLogger instance
if (identToken == null || !identToken.getText().equals(CLIENT_LOGGER)) {
return;
}
// LITERAL_NEW node always has ELIST node below
TokenUtil.findFirstTokenByPredicate(literalNewToken.findFirstToken(TokenTypes.ELIST), exprToken -> {
// Skip check if not EXPR node or if has no DOT node below. EXPR always has children below
if (exprToken.getType() != TokenTypes.EXPR || exprToken.getFirstChild().getType() != TokenTypes.DOT) {
return false;
}
// Check instantiation of ClientLogger
final String containerClassName = FullIdent.createFullIdent(exprToken.getFirstChild()).getText();
// Add suffix of '.class' at the end of class name
final String className = classNameDeque.peekFirst();
if (!containerClassName.equals(className + ".class")) {
log(exprToken, String.format("Not newing a ClientLogger with matching class name. Use ''%s.class'' instead of ''%s''", className, containerClassName));
}
return true;
});
}

/**
* 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,15 +98,15 @@
<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 -->
<suppress checks="com\.azure\.tools\.checkstyle\.checks\.(ExternalDependencyExposedCheck|NoImplInPublicAPI|ServiceClientInstantiationCheck|ServiceClientBuilderCheck|ServiceInterfaceCheck|JavaDocFormatting|JavadocThrowsChecks|EnforceFinalFieldsCheck|ThrowFromClientLoggerCheck)"
files=".*[/\\]implementation[/\\].*\.java"/>

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

<!-- Don't check for JavaDocPackage in samples or tests -->
Expand Down Expand Up @@ -139,4 +139,11 @@
<!-- CodeSnippet Suppression for now, which need code owner's attention -->
<suppress checks="com.azure.tools.checkstyle.checks.JavadocCodeSnippetCheck" files="com.azure.data.appconfiguration.ConfigurationAsyncClient.java"/>
<suppress checks="com.azure.tools.checkstyle.checks.JavadocCodeSnippetCheck" files="com.azure.messaging.eventhubs.EventData.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"/>
<!-- Public API already released -->
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files="CertificateUtil.java"/>
Copy link
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
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
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 @@ -351,5 +351,15 @@ page at http://checkstyle.sourceforge.net/config.html -->
2) Methods arguments should be concatenated by dash '-'. Ex. string-string for methodName(String s, String s2)
3) Use '#' to concatenate 1) and 2), ex packageName.className.methodName#string-string -->
<module name="com.azure.tools.checkstyle.checks.JavadocCodeSnippetCheck"/>

<!--CUSTOM CHECKS-->
<!-- Good Logging Practice
1) ClientLogger in public API should all named 'logger', public API classes are those classes that are declared
as public and that do not exist in an implementation package or subpackage.
2) ClientLogger should be non-static instance logger
3) Should not use any external logger class, only use ClientLogger. No slf4j, log4j, or other logging imports are allowed.
'System.out' and 'System.err' is not allowed as well.
4) All classes should use ClientLogger as logger only but except ClientLogger itself -->
<module name="com.azure.tools.checkstyle.checks.GoodLoggingCheck"/>
</module>
</module>
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