Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements a slf4j logger impl as default logger in azure core #7298

Merged
merged 13 commits into from
Jan 16, 2020

Conversation

sima-zhu
Copy link
Contributor

@sima-zhu sima-zhu commented Jan 9, 2020

No description provided.

void write(StringBuilder buf, Throwable t) {
PrintStream targetStream = System.out;

targetStream.println(buf.toString());
Copy link
Member

Choose a reason for hiding this comment

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

Should make the entire print statement a single operation to prevent race conditions interleaving messages

Copy link
Contributor Author

Choose a reason for hiding this comment

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

https://stackoverflow.com/questions/9459657/is-multi-thread-output-from-system-out-println-interleaved

Make the print process atomic so the log message will not intervene by other message, or print throwable to other places.

Copy link
Member

Choose a reason for hiding this comment

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

The individual print statement shouldn't interleave but right now we print the message and stack trace in two separate calls which could result in the following scenario.

Two exceptions are being logged, problemA and problemB, logging could have the following outcome

  • problemA message
  • problemB message
  • problemA stack trace
  • problemB stack trace

Instead we should build the message + stack trace and log them in one print call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

    void write(StringBuilder buf, Throwable t) {
        if (t != null) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            t.printStackTrace(pw);
            String sStackTrace = sw.toString();
            buf.append(sStackTrace);
        }
        System.out.println(buf.toString());
    }

Comment on lines 34 to 39
/**
* Log a message at the TRACE level. It is currently not supported.
*
* @param msg the message string to be logged
* @throws UnsupportedOperationException It is currently not supported.
*/
Copy link
Member

Choose a reason for hiding this comment

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

Let's remove these Javadocs and just use

/**
 * {@inheritDoc}
 */

import org.slf4j.Logger;
import org.slf4j.Marker;

abstract class DefaultLoggerBase implements Logger {
Copy link
Member

Choose a reason for hiding this comment

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

javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed.

Comment on lines 177 to 178
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
Copy link
Member

Choose a reason for hiding this comment

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

Both StringWriter and PrintWriter should be closed after use

Copy link
Contributor Author

Choose a reason for hiding this comment

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

StringWriter does not need to close as it is a no op.
https://stackoverflow.com/questions/14542535/will-not-closing-a-stringwriter-cause-a-leak

Closed the PrintStream

* (non-parameterized) log messages.
*
* @param level
* One of the LOG_LEVEL_XXX constants defining the log level
Copy link
Member

Choose a reason for hiding this comment

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

nit: fix formatting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

*/
@Override
public boolean isDebugEnabled() {
throw new UnsupportedOperationException();
Copy link
Member

Choose a reason for hiding this comment

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

This should be supported since we have an implementation for debug(String format, Object.. argArray) on line 61.

*/
@Override
public void debug(final String msg) {
throw new UnsupportedOperationException();
Copy link
Member

Choose a reason for hiding this comment

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

This should be supported too as we have support for debug overload with formatted strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I enabled the ones used by ClientLogger.

*/
@Override
public String getName() {
throw new UnsupportedOperationException();
Copy link
Member

Choose a reason for hiding this comment

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

This should be supported. We should return the name of this logger instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Talked offline. Will create an issue and put Up-for-grabs label.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

github.com//issues/7341

*/
@Override
public boolean isInfoEnabled() {
throw new UnsupportedOperationException();
Copy link
Member

Choose a reason for hiding this comment

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

All is*Enabled() methods should be supported.

*/
@Override
public void info(final String msg) {
throw new UnsupportedOperationException();
Copy link
Member

Choose a reason for hiding this comment

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

These should be implemented too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above

PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
buf.append(sw.toString());
pw.close();
Copy link
Member

Choose a reason for hiding this comment

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

Use try-with-resources to ensure the resource is always closed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since StringWriter has no-op close(), I only move pw to try with resource. Thanks for pointing this out!

* @return The log level.
*/
private LogLevel getConfiguredLogLevel() {
if (isFromEnv) {
Copy link
Member

Choose a reason for hiding this comment

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

If we have the default logger implement the isDebugEnabled and other similar APIs there would be no need for isFromEnv nor would there need to be any different handling here.

Copy link
Member

Choose a reason for hiding this comment

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

The only reason the environment logging level was passed around was to handle the fact that there was a two tiered structure of SLF4J logging level and environment logging level, not that this is being merged a lot of the logic in this class and within HttpLoggingPolicy could be cleaned up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@sima-zhu sima-zhu requested a review from alzimmermsft January 10, 2020 22:44
Comment on lines 60 to 62
String logLevelStr = Configuration.getGlobalConfiguration().get(AZURE_LOG_LEVEL);
LogLevel currentLogLevel = LogLevel.fromString(logLevelStr);
return LogLevel.VERBOSE.getLogLevel() >= currentLogLevel.getLogLevel();
Copy link
Member

Choose a reason for hiding this comment

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

This code is common for all is*Enabled() methods. You can extract this out to a method to reduce duplication.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


/**
* {@inheritDoc}
* @param format The formattable message to log.
Copy link
Member

Choose a reason for hiding this comment

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

Combination of inheritdoc and @param docs don't work correctly. Just use @inheritDoc and skip @param docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -36,6 +35,7 @@
*/
public class ClientLogger {
private final Logger logger;
private final boolean isFromEnv;
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you need this anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -36,6 +35,7 @@
*/
public class ClientLogger {
private final Logger logger;
private final boolean isFromEnv;
Copy link
Member

Choose a reason for hiding this comment

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

Is this being used by anything other than the instantiation in the constructor? If not let's remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 60 to 61
String logLevelStr = Configuration.getGlobalConfiguration().get(AZURE_LOG_LEVEL);
LogLevel currentLogLevel = LogLevel.fromString(logLevelStr);
Copy link
Member

Choose a reason for hiding this comment

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

Should this logic be abstracted into LoggingUtils or as a private static method on this class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have a private helper method in class. Env log level is only needed for default logger, so it is ok to leave it here for current use.

String dateTime = getFormattedDate();
String threadName = Thread.currentThread().getName();
String levelName = level.name();
StringBuilder buf = new StringBuilder(32);
Copy link
Member

Choose a reason for hiding this comment

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

Any reason to instantiate the StringBuilder with an internal capacity of 32? Instead could it be instantiated with a capacity large enough to hold the initial string being written to it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is inherited from simple logger.
The message we know definitely will be printed out is:
2020-01-09 12:35 [main] [Informational]

This is 39 character long. I can put 64 instead of 32 characters to minimize capacity increase.

<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files="ClientLogger.java"/>
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files="DefaultLogger.java"/>
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files="DefaultLoggerBase.java"/>
Copy link
Member

Choose a reason for hiding this comment

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

This line can now be removed as DefaultLoggerBase does not exist.

@mitchdenny
Copy link
Contributor

/check-enforcer reset

Copy link
Member

@JonathanGiles JonathanGiles left a comment

Choose a reason for hiding this comment

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

Even if the functionality in DefaultLogger is unused, it is trivial to implement and should be done now rather than when we get bug reports from customers.

* {@inheritDoc}
*/
@Override
public void trace(final String msg, final Throwable t) {
Copy link
Member

Choose a reason for hiding this comment

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

Why overload all of these methods to throw an exception when the base class has an actual useful implementation? Look at https://github.com/qos-ch/slf4j/blob/master/slf4j-api/src/main/java/org/slf4j/helpers/MarkerIgnoringBase.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.

Done

@mitchdenny
Copy link
Contributor

/check-enforcer reset

@mitchdenny
Copy link
Contributor

/check-enforcer evaluate

* Construct DefaultLogger for the given class name.
*
* @param className Class name creating the logger.
* @throws RuntimeException it is an error.
Copy link
Member

Choose a reason for hiding this comment

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

Will this throw an exception now that you have a catch block?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not logger throw an exception. Good catch. Will update the JavaDoc

@@ -153,6 +153,7 @@

<!-- Any code in any package, it should never be a 'throw' keyword in the client library codebase except for in the client logger -->
<suppress checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" files=".*[/\\]com[/\\]azure[/\\]core[/\\]util[/\\]logging[/\\]*"/>
<suppress checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" files=".*[/\\]com[/\\]azure[/\\]core[/\\]implementation[/\\]logging[/\\]*"/>
Copy link
Member

Choose a reason for hiding this comment

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

Now that all methods are implemented in DefaultLogger this exception is no longer required.

@sima-zhu sima-zhu requested a review from alzimmermsft January 15, 2020 20:48
@sima-zhu sima-zhu merged commit 6b2c11e into Azure:master Jan 16, 2020
@@ -144,7 +133,7 @@ public void onlyLogExceptionMessage() {
*/
@Test
public void logExceptionStackTrace() {
String logMessage = "This is an exception";
String logMessage = "This is an exception fdsafdafdomcklamfd fdsafdafmlkdfmalsf fdsafdcacdalmd";
Copy link
Member

Choose a reason for hiding this comment

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

Is this an error message?

@sima-zhu sima-zhu deleted the addlogimpl branch July 13, 2020 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants