-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementation & tests for SLF4J flogger backend
Closes #61 RELNOTES=Adds a new `slf4j-backend-factory` artifact ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=238273653
- Loading branch information
1 parent
ac95fe8
commit 2a67023
Showing
6 changed files
with
469 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ package_group( | |
"//api/...", | ||
"//google/...", | ||
"//log4j/...", | ||
"//slf4j/...", | ||
"//tools/...", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Copyright 2018 Google Inc. All Rights Reserved. | ||
# | ||
# Description: | ||
# Flogger slf4j backend (google.github.io/flogger). | ||
|
||
package(default_visibility = ["//:internal"]) | ||
|
||
SLF4J_BACKEND_SRCS = glob(["src/main/java/**/*.java"]) | ||
|
||
java_library( | ||
name = "slf4j_backend", | ||
srcs = SLF4J_BACKEND_SRCS, | ||
javacopts = ["-source 1.6 -target 1.6"], | ||
tags = ["maven_coordinates=com.google.flogger:flogger-slf4j-backend:${project.version}"], | ||
deps = [ | ||
"//api", | ||
"//api:system_backend", | ||
"@google_bazel_common//third_party/java/slf4j_api", | ||
], | ||
) | ||
|
||
load("//tools:maven.bzl", "pom_file") | ||
|
||
pom_file( | ||
name = "pom", | ||
artifact_id = "flogger-slf4j-backend", | ||
artifact_name = "Flogger SLF4J Backend", | ||
targets = [":slf4j_backend"], | ||
) | ||
|
||
load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") | ||
|
||
javadoc_library( | ||
name = "javadoc", | ||
srcs = SLF4J_BACKEND_SRCS, | ||
root_packages = ["com.google.common.flogger.backend.slf4j"], | ||
deps = [":slf4j_backend"], | ||
) | ||
|
||
# ---- Unit Tests ---- | ||
|
||
load("@google_bazel_common//testing:test_defs.bzl", "gen_java_tests") | ||
|
||
gen_java_tests( | ||
name = "slf4j_tests", | ||
srcs = glob(["src/test/java/**/*.java"]), | ||
deps = [ | ||
":slf4j_backend", | ||
"//api", | ||
"//api:testing", | ||
"@google_bazel_common//third_party/java/junit", | ||
"@google_bazel_common//third_party/java/mockito", | ||
"@google_bazel_common//third_party/java/slf4j_api", | ||
], | ||
) |
54 changes: 54 additions & 0 deletions
54
slf4j/src/main/java/com/google/common/flogger/backend/slf4j/Slf4jBackendFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* Copyright (C) 2018 The Flogger Authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.common.flogger.backend.slf4j; | ||
|
||
import com.google.common.flogger.backend.LoggerBackend; | ||
import com.google.common.flogger.backend.system.BackendFactory; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* BackendFactory for SLF4J. | ||
* | ||
* <p>To configure this backend for Flogger set the following system property (also see {@link | ||
* com.google.common.flogger.backend.system.DefaultPlatform}): | ||
* | ||
* <ul> | ||
* <li>{@code flogger.backend_factory= | ||
* com.google.common.flogger.backend.slf4j.Slf4jBackendFactory#getInstance}. | ||
* </ul> | ||
*/ | ||
public final class Slf4jBackendFactory extends BackendFactory { | ||
|
||
private static final Slf4jBackendFactory INSTANCE = new Slf4jBackendFactory(); | ||
|
||
/** This method is expected to be called via reflection (and might otherwise be unused). */ | ||
public static BackendFactory getInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
@Override | ||
public LoggerBackend create(String loggingClassName) { | ||
return new Slf4jLoggerBackend(LoggerFactory.getLogger(loggingClassName.replace('$', '.'))); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "SLF4J backend"; | ||
} | ||
|
||
private Slf4jBackendFactory() {} | ||
} |
219 changes: 219 additions & 0 deletions
219
slf4j/src/main/java/com/google/common/flogger/backend/slf4j/Slf4jLoggerBackend.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
/* | ||
* Copyright (C) 2018 The Flogger Authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.common.flogger.backend.slf4j; | ||
|
||
import com.google.common.flogger.backend.LogData; | ||
import com.google.common.flogger.backend.LoggerBackend; | ||
import com.google.common.flogger.backend.Metadata; | ||
import com.google.common.flogger.backend.SimpleMessageFormatter; | ||
import java.util.logging.Level; | ||
import org.slf4j.Logger; | ||
|
||
/** A logging backend that uses slf4j to output log statements. */ | ||
final class Slf4jLoggerBackend extends LoggerBackend | ||
implements SimpleMessageFormatter.SimpleLogHandler { | ||
|
||
private final Logger logger; | ||
|
||
Slf4jLoggerBackend(Logger logger) { | ||
if (logger == null) { | ||
throw new NullPointerException("logger is required"); | ||
} | ||
this.logger = logger; | ||
} | ||
|
||
// represents the log levels supported by SLF4J, used internally to dispatch calls accordingly | ||
private enum Slf4jLogLevel { | ||
TRACE, | ||
DEBUG, | ||
INFO, | ||
WARN, | ||
ERROR | ||
} | ||
|
||
/** | ||
* Adapts the JUL level to SLF4J level per the below mapping: | ||
* | ||
* <table> | ||
* <tr> | ||
* <th>JUL</th> | ||
* <th>SLF4J</th> | ||
* </tr> | ||
* <tr> | ||
* <td>FINEST</td><td>TRACE</td> | ||
* </tr><tr> | ||
* <td>FINER</td><td>TRACE</td> | ||
* </tr> | ||
* <tr> | ||
* <td>FINE</td><td>DEBUG</td> | ||
* </tr> | ||
* <tr> | ||
* <td>CONFIG</td><td>DEBUG</td> | ||
* </tr> | ||
* <tr> | ||
* <td>INFO</td> | ||
* <td>INFO</td> | ||
* </tr> | ||
* <tr> | ||
* <td>WARNING</td> | ||
* <td>WARN</td> | ||
* </tr> | ||
* <tr> | ||
* <td>SEVERE</td> | ||
* <td>ERROR</td> | ||
* </tr> | ||
* </table> | ||
* | ||
* <p>Custom JUL levels are mapped to the next-lowest standard JUL level; for example, a custom | ||
* level at 750 (between INFO:800 and CONFIG:700) would map to the same as CONFIG (DEBUG). | ||
* | ||
* <p>It isn't expected that the JUL levels 'ALL' or 'OFF' are passed into this method; doing so | ||
* will throw an IllegalArgumentException, as those levels are for configuration, not logging | ||
* | ||
* @param level the JUL level to map; any standard or custom JUL level, except for ALL or OFF | ||
* @return the MappedLevel object representing the SLF4J adapters appropriate for the requested | ||
* log level; never null. | ||
*/ | ||
private static Slf4jLogLevel mapToSlf4jLogLevel(Level level) { | ||
// Performance consideration: mapToSlf4jLogLevel is a very hot method, called even when | ||
// logging is disabled. Allocations (and other latency-introducing constructs) should be avoided | ||
int requestedLevel = level.intValue(); | ||
|
||
// Flogger shouldn't allow ALL or OFF to be used for logging | ||
// if Flogger does add this check to the core library it can be removed here (and should be, | ||
// as this method is on the critical performance path for determining whether log statements | ||
// are disabled, hence called for all log statements) | ||
if (requestedLevel == Level.ALL.intValue() || requestedLevel == Level.OFF.intValue()) { | ||
throw new IllegalArgumentException("Unsupported log level: " + level); | ||
} | ||
|
||
if (requestedLevel < Level.FINE.intValue()) { | ||
return Slf4jLogLevel.TRACE; | ||
} | ||
|
||
if (requestedLevel < Level.INFO.intValue()) { | ||
return Slf4jLogLevel.DEBUG; | ||
} | ||
|
||
if (requestedLevel < Level.WARNING.intValue()) { | ||
return Slf4jLogLevel.INFO; | ||
} | ||
|
||
if (requestedLevel < Level.SEVERE.intValue()) { | ||
return Slf4jLogLevel.WARN; | ||
} | ||
|
||
return Slf4jLogLevel.ERROR; | ||
} | ||
|
||
@Override | ||
public String getLoggerName() { | ||
return logger.getName(); | ||
} | ||
|
||
@Override | ||
public boolean isLoggable(Level level) { | ||
// Performance consideration: isLoggable is a very hot method, called even when logging is | ||
// disabled. Allocations (and any other latency-introducing constructs) should be avoided | ||
Slf4jLogLevel slf4jLogLevel = mapToSlf4jLogLevel(level); | ||
|
||
// dispatch to each level-specific method as SLF4J doesn't expose a logger.log( level, ... ) | ||
switch (slf4jLogLevel) { | ||
case TRACE: | ||
return logger.isTraceEnabled(); | ||
case DEBUG: | ||
return logger.isDebugEnabled(); | ||
case INFO: | ||
return logger.isInfoEnabled(); | ||
case WARN: | ||
return logger.isWarnEnabled(); | ||
case ERROR: | ||
return logger.isErrorEnabled(); | ||
} | ||
throw new AssertionError("Unknown SLF4J log level: " + slf4jLogLevel); | ||
} | ||
|
||
@Override | ||
public void log(LogData data) { | ||
SimpleMessageFormatter.format(data, this); | ||
} | ||
|
||
@Override | ||
public void handleError(RuntimeException exception, LogData badData) { | ||
StringBuilder errorMsg = new StringBuilder(200); | ||
errorMsg.append("LOGGING ERROR: "); | ||
errorMsg.append(exception.getMessage()); | ||
errorMsg.append('\n'); | ||
formatBadLogData(badData, errorMsg); | ||
|
||
// log at ERROR to ensure visibility | ||
logger.error(errorMsg.toString(), exception); | ||
} | ||
|
||
// based on com.google.common.flogger.backend.system.AbstractLogRecord.safeAppend | ||
private static void formatBadLogData(LogData data, StringBuilder out) { | ||
out.append(" original message: "); | ||
if (data.getTemplateContext() == null) { | ||
out.append(data.getLiteralArgument()); | ||
} else { | ||
// We know that there's at least one argument to display here. | ||
out.append(data.getTemplateContext().getMessage()); | ||
out.append("\n original arguments:"); | ||
for (Object arg : data.getArguments()) { | ||
out.append("\n ").append(SimpleMessageFormatter.safeToString(arg)); | ||
} | ||
} | ||
Metadata metadata = data.getMetadata(); | ||
if (metadata.size() > 0) { | ||
out.append("\n metadata:"); | ||
for (int n = 0; n < metadata.size(); n++) { | ||
out.append("\n "); | ||
out.append(metadata.getKey(n).getLabel()).append(": ").append(metadata.getValue(n)); | ||
} | ||
} | ||
out.append("\n level: ").append(data.getLevel()); | ||
out.append("\n timestamp (nanos): ").append(data.getTimestampNanos()); | ||
out.append("\n class: ").append(data.getLogSite().getClassName()); | ||
out.append("\n method: ").append(data.getLogSite().getMethodName()); | ||
out.append("\n line number: ").append(data.getLogSite().getLineNumber()); | ||
} | ||
|
||
@Override | ||
public void handleFormattedLogMessage(Level level, String message, Throwable thrown) { | ||
Slf4jLogLevel slf4jLogLevel = mapToSlf4jLogLevel(level); | ||
|
||
// dispatch to each level-specific method, as SLF4J doesn't expose a logger.log( level, ... ) | ||
switch (slf4jLogLevel) { | ||
case TRACE: | ||
logger.trace(message, thrown); | ||
return; | ||
case DEBUG: | ||
logger.debug(message, thrown); | ||
return; | ||
case INFO: | ||
logger.info(message, thrown); | ||
return; | ||
case WARN: | ||
logger.warn(message, thrown); | ||
return; | ||
case ERROR: | ||
logger.error(message, thrown); | ||
return; | ||
} | ||
throw new AssertionError("Unknown SLF4J log level: " + slf4jLogLevel); | ||
} | ||
} |
Oops, something went wrong.
2a67023
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 very nice to see this merged. 👍
It would be great to have a release containing this feature!
2a67023
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.
Done :)
2a67023
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.
Thank you very much! :)
2a67023
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.
No problem, and sorry for the delay. I've been too busy to look at this myself due to other (non logging) project stuff, and Ron's been "holding the fort" for release stuff. I'll try and ensure things don't take as long in future :/