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

Implement log formatter #81

Merged
merged 3 commits into from
Oct 31, 2016
Merged

Implement log formatter #81

merged 3 commits into from
Oct 31, 2016

Conversation

QubitPi
Copy link
Contributor

@QubitPi QubitPi commented Oct 28, 2016

Issue #74

*/
public class JsonLogFormatter implements LogFormatter {
private static final Logger LOG = LoggerFactory.getLogger(RequestLog.class);
private final ObjectMapper mapper = new ObjectMapper();
Copy link
Contributor

Choose a reason for hiding this comment

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

This ObjectMapper should be configured with the same modules as the original mapper in RequestLog (which incidentally also needs to be removed).

/**
* Takes in a log block and returns a formatted String to be logged.
*
* @param logBlock the log block that represents a log and that will be formatted in to the needed format.
Copy link
Contributor

Choose a reason for hiding this comment

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

Should have two spaces between parameter name and description.

*/
public class LogFormatterProvider {

private static final Logger LOG = LoggerFactory.getLogger(SystemConfigProvider.class);
Copy link
Contributor

Choose a reason for hiding this comment

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

Incorrect class (should be LogFormatterProvider.class not SystemConfigProvider.class


private static final Logger LOG = LoggerFactory.getLogger(SystemConfigProvider.class);

private static final String LOG_FORMATTER_IMPL_KEY = "fili__log_formatter_impl";
Copy link
Contributor

Choose a reason for hiding this comment

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

This shouldn't be prefixed with fili. We should be getting the module name from the SystemConfig.

Copy link
Contributor

Choose a reason for hiding this comment

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

(That's what SystemConfig::getPackageVariableName does).

String logFormatterImplementation = System.getenv(LOG_FORMATTER_IMPL_KEY);
try {
if (logFormatterImplementation == null) {
logFormatterImplementation = System.getProperty(LOG_FORMATTER_IMPL_KEY, DEFAULT_LOG_FORMATTER_IMPL);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think here you should actually be using an instance of SystemConfig:

logFormatterImplementation = SystemConfigProvider.getInstance().getStringProperty(
        SYSTEM_CONFIG.getPackageVariableName(LOG_FORMATTER_IMPL_KEY),
        DEFAULT_LOG_FORMATTER_IMPL
);

Also, I think line 35 can be removed, and we can drop the null check.

@michael-mclawhorn Could probably can verify that for me.

The reason the SystemConfigProvider accesses the environment directly, is because it can't very well ask itself about its configuration parameter!

Everything else should go through the SystemConfig object though.


private static final String LOG_FORMATTER_IMPL_KEY = SystemConfigProvider.getInstance().getStringProperty(
SYSTEM_CONFIG.getPackageVariableName("log_formatter_impl"),
"fili__log_formatter_impl"
Copy link
Contributor

Choose a reason for hiding this comment

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

The second argument to getStringProperty is not a fully qualified config variable, it's a default setting for the config parameter. In other words, the second argument to getStringProperty should be DEFAULT_LOG_FORMATTER_IMPL not fili__log_formatter_impl.

@archolewa
Copy link
Contributor

👍

Copy link
Collaborator

@cdeszaq cdeszaq left a comment

Choose a reason for hiding this comment

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

Changelog entry needed. Likely under the Added section

*/
public class JsonLogFormatter implements LogFormatter {
private static final Logger LOG = LoggerFactory.getLogger(RequestLog.class);
private final ObjectMapper objectMapper = new ObjectMapper();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not a big deal, but it would be better to just construct this in the constructor, since that's where it's being configured anyways. Colcating the creation and configuration code would be good.

} catch (JsonProcessingException jsonProcessingException) {
String message = String.format("Exporting mega log line: '%s' to JSON failed.", logBlock);
LOG.warn(message, jsonProcessingException);
return message;
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems strange that we'd want to log the logBlock as this error message. Perhaps we should just return it's toString value instead of this message?

private static final String DEFAULT_LOG_FORMATTER_IMPL = JsonLogFormatter.class.getCanonicalName();

private static final String LOG_FORMATTER_IMPL_KEY = SystemConfigProvider.getInstance().getStringProperty(
SYSTEM_CONFIG.getPackageVariableName("log_formatter_impl"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we expand this to implementation? In general, we try to avoid abbreviations if we can so that the code is more understandable and clear.

if (LOG_FORMATTER != null) {
return LOG_FORMATTER;
}
String logFormatterImplementation = System.getenv(LOG_FORMATTER_IMPL_KEY);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This construction allows for the LOG_FORMATTER singleton holder to be unset if somehow something calls getInstance before this class does. It shouldn't happen, but it would be good if we can insulate ourselves. A better construction is something like this:

public String getInstance() {
    if (MY_INSTANCE == null) {
        // create myInstance
        MY_INSTANCE = myInstance;
    }
    return MY_INSTANCE;
}

By having only 1 return path, it's easier to reason about having set the singleton holder.

And if you want to get even more paranoid, you can have the body of that if be it's own synchronized helper method (which should also check if it's already set).

}
return (LogFormatter) Class.forName(logFormatterImplementation).newInstance();
} catch (Exception e) {
LOG.error("Exception while loading Log formatter: {}", e.getMessage(), e);
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's no use in having the exception message as it's own parameter, and in fact it's actively hiding information in this call because you only have 1 placeholder in the log message template. (ie. e will not get logged)

Instead, since SLF4J log calls can natively handle exceptions, just have a call like this: LOG.error("message", e), passing the exception as the only parameter.

return (LogFormatter) Class.forName(logFormatterImplementation).newInstance();
} catch (Exception e) {
LOG.error("Exception while loading Log formatter: {}", e.getMessage(), e);
throw new IllegalStateException(e);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should make sure to include the message we're logging in the exception as a message as well.

*
* @return a formatted String to be logged
*/
String write(LogBlock logBlock);
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's strange that this method is called write and not format when the interface is a Formatter. Can we change the name of the method to format? (it's also not supposed to be writing anything, only returning the LogBlock as a formatted string.)

LOG.warn(msg, jpe);
return msg;
}
return LogFormatterProvider.getInstance().write(current.info);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since there's now no longer any requirement that this return JSON, we should update the JavaDoc on this method to reflect that as well.

Copy link
Collaborator

@cdeszaq cdeszaq left a comment

Choose a reason for hiding this comment

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

Just a couple of small things and this'll be good to go I think!

@@ -10,6 +10,8 @@ Current

### Added:

- [Customize Logging Format](https://github.com/yahoo/fili/pull/81)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should mention that it's the RequestLog, not all logs in general

return (LogFormatter) Class.forName(logFormatterImplementation).newInstance();
} catch (Exception exception) {
LOG.error("Exception while loading Log formatter: {}", exception);
throw new IllegalStateException(exception.getMessage());
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should always include the causing exception as an exception's Cause when creating a new exception.

@michael-mclawhorn
Copy link
Contributor

I'd like to see some tests. Test that the Json log formatter produces something reasonable from a LogBlock at the very least. Test that you get the expected LogFormatter based on configuration would also be nice. (if you implement my suggestion with definalizing the LogFormatter, you could test with one and then with the other by tweaking configuration)

* The instance of the Log Formatter.
*/
private static final LogFormatter LOG_FORMATTER = getInstance();

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not convinced that final is the correct behavior here. SystemConfig was intended to allow bootstrapping to more sophisticated configurations at runtime. I'm not sure that it wouldn't make sense to allow this to do the same.

Copy link
Collaborator

Choose a reason for hiding this comment

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

There are many cases where we're pulling in config'd things and making them final, so I think this would be the least of the worries in the "updatable runtime config" world. That said, to pull such a config mechanism off, either this would need to be a Supplier<LogFormatter>, or there would need to be a different out-of-band mechanism for updating this field. Either way, having a field be non-runtime-updatable (ie. final) would still be perfectly allowable, if inadvisable) so I don't see any trouble with it (personally.)

*/
public static LogFormatter getInstance() {
if (LOG_FORMATTER == null) {
String logFormatterImplementation = System.getenv(LOG_FORMATTER_IMPLEMENTATION_KEY);
Copy link
Contributor

@archolewa archolewa Oct 31, 2016

Choose a reason for hiding this comment

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

We shouldn't be going to the environment directly at all. The default implementation of SystemConfig (LayeredSystemConfig) already goes to the environment first, and if a customer introduces their own custom system configuration that changes the priority of the environment (or doesn't want to use it all) they'll get incorrect behavior here.

We should only be going to the SystemConfigProvider.

Copy link
Collaborator

@cdeszaq cdeszaq left a comment

Choose a reason for hiding this comment

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

👍 after @archolewa's concern about the config value gets addressed

@archolewa
Copy link
Contributor

archolewa commented Oct 31, 2016

Reafirrming my 👍. Running the tests locally, and then I'll merge it in.

@archolewa archolewa merged commit d5a5ea4 into yahoo:master Oct 31, 2016
@QubitPi QubitPi deleted the implement-logFormatter branch October 31, 2016 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants