-
Notifications
You must be signed in to change notification settings - Fork 96
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
Conversation
*/ | ||
public class JsonLogFormatter implements LogFormatter { | ||
private static final Logger LOG = LoggerFactory.getLogger(RequestLog.class); | ||
private final ObjectMapper mapper = new ObjectMapper(); |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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
.
👍 |
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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"), |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this 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) |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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.
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(); | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
.
There was a problem hiding this 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
Reafirrming my 👍. Running the tests locally, and then I'll merge it in. |
Issue #74