![JAR search and dependency download from the Maven repository](/logo.png)
com.microsoft.azure.storage.blob.LoggingFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of azure-storage-blob Show documentation
Show all versions of azure-storage-blob Show documentation
The Azure Storage Java Blob library.
/*
* Copyright Microsoft Corporation
*
* 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.microsoft.azure.storage.blob;
import com.microsoft.rest.v2.http.HttpPipeline;
import com.microsoft.rest.v2.http.HttpPipelineLogLevel;
import com.microsoft.rest.v2.http.HttpRequest;
import com.microsoft.rest.v2.http.HttpResponse;
import com.microsoft.rest.v2.policy.RequestPolicy;
import com.microsoft.rest.v2.policy.RequestPolicyFactory;
import com.microsoft.rest.v2.policy.RequestPolicyOptions;
import io.reactivex.Single;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This is a factory which creates policies in an {@link HttpPipeline} for logging requests and responses. In most
* cases, it is sufficient to configure an object of the {@link LoggingOptions} type and set those as a field on a
* {@link PipelineOptions} structure to configure a default pipeline. The factory and policy must only be used directly
* when creating a custom pipeline.
*/
public final class LoggingFactory implements RequestPolicyFactory {
private static final Logger forceLogger = Logger.getLogger("Azure Storage Java SDK");
private static final org.slf4j.Logger slf4jLogger = LoggerFactory.getLogger("Azure Storage Java SDK");
private static final Map javaLogLevelMap = new HashMap<>();
private static boolean defaultLoggerLoaded;
static {
try {
forceLogger.setLevel(Level.WARNING);
// Create the logs directory if it doesn't exist.
File logDir = new File(System.getProperty("java.io.tmpdir") + "AzureStorageJavaSDKLogs");
if (!logDir.exists()) {
if (!logDir.mkdir()) {
throw new Exception("Could not create logs directory");
}
}
/*
"/" the local pathname separator
"%t" the system temporary directory
"%h" the value of the "user.home" system property
"%g" the generation number to distinguish rotated logs
"%u" a unique number to resolve conflicts
"%%" translates to a single percent sign "%"
10MB files, 5 files
true- append mode
*/
FileHandler handler = new FileHandler("%t/AzureStorageJavaSDKLogs/%u%g", 10 * Constants.MB, 5, false);
handler.setLevel(Level.WARNING);
forceLogger.addHandler(handler);
javaLogLevelMap.put(HttpPipelineLogLevel.ERROR, Level.SEVERE);
javaLogLevelMap.put(HttpPipelineLogLevel.WARNING, Level.WARNING);
javaLogLevelMap.put(HttpPipelineLogLevel.INFO, Level.INFO);
defaultLoggerLoaded = true;
/*
If we can't setup default logging, there's nothing we can do. We shouldn't interfere with the rest of logging.
*/
} catch (Exception e) {
defaultLoggerLoaded = false;
System.err.println("Azure Storage default logging could not be configured due to the following exception: "
+ e);
}
}
private final LoggingOptions loggingOptions;
/**
* Creates a factory which can create LoggingPolicy objects to insert in the pipeline. This will allow for logging
* requests and responses.
*
* @param loggingOptions
* The configurations for this factory. Null will indicate use of the default options.
*/
public LoggingFactory(LoggingOptions loggingOptions) {
this.loggingOptions = loggingOptions == null ? LoggingOptions.DEFAULT : loggingOptions;
}
@Override
public RequestPolicy create(RequestPolicy next, RequestPolicyOptions options) {
return new LoggingPolicy(this, next, options);
}
private final class LoggingPolicy implements RequestPolicy {
private final LoggingFactory factory;
private final RequestPolicy nextPolicy;
private final RequestPolicyOptions options;
// The following fields are not final because they are updated by the policy.
private int tryCount;
private long operationStartTime;
private long requestStartTime;
/**
* Creates a policy which configures the logging behavior within the
* {@link com.microsoft.rest.v2.http.HttpPipeline}.
*
* @param nextPolicy
* {@link RequestPolicy}
* @param options
* {@link RequestPolicyOptions}
* @param factory
* {@link LoggingFactory}
*/
private LoggingPolicy(LoggingFactory factory, RequestPolicy nextPolicy, RequestPolicyOptions options) {
this.factory = factory;
this.nextPolicy = nextPolicy;
this.options = options;
}
/**
* Logs as appropriate.
*
* @param request
* The request to log.
*
* @return A {@link Single} representing the {@link HttpResponse} that will arrive asynchronously.
*/
@Override
public Single sendAsync(final HttpRequest request) {
this.tryCount++;
this.requestStartTime = System.currentTimeMillis();
if (this.tryCount == 1) {
this.operationStartTime = requestStartTime;
}
if (this.shouldLog(HttpPipelineLogLevel.INFO)) {
String logMessage = String.format("'%s'==> OUTGOING REQUEST (Try number='%d')%n", request.url(),
this.tryCount);
this.log(HttpPipelineLogLevel.INFO, logMessage);
}
return nextPolicy.sendAsync(request)
.doOnError(throwable -> {
if (this.shouldLog(HttpPipelineLogLevel.ERROR)) {
String logMessage = String.format(
"Unexpected failure attempting to make request.%nError message:'%s'%n",
throwable.getMessage());
this.log(HttpPipelineLogLevel.ERROR, logMessage);
}
})
.doOnSuccess(response -> {
long requestEndTime = System.currentTimeMillis();
long requestCompletionTime = requestEndTime - requestStartTime;
long operationDuration = requestEndTime - operationStartTime;
HttpPipelineLogLevel currentLevel = HttpPipelineLogLevel.INFO;
String logMessage = Constants.EMPTY_STRING;
if (this.shouldLog(HttpPipelineLogLevel.INFO)) {
// Assume success and default to informational logging.
logMessage = "Successfully Received Response" + System.lineSeparator();
}
// If the response took too long, we'll upgrade to warning.
if (requestCompletionTime >=
factory.loggingOptions.minDurationToLogSlowRequestsInMs()) {
// Log a warning if the try duration exceeded the specified threshold.
if (this.shouldLog(HttpPipelineLogLevel.WARNING)) {
currentLevel = HttpPipelineLogLevel.WARNING;
logMessage = String.format(Locale.ROOT,
"SLOW OPERATION. Duration > %d ms.%n",
factory.loggingOptions.minDurationToLogSlowRequestsInMs());
}
}
if (((response.statusCode() >= 400 && response.statusCode() <= 499) &&
(response.statusCode() != HttpURLConnection.HTTP_NOT_FOUND &&
response.statusCode() != HttpURLConnection.HTTP_CONFLICT &&
response.statusCode() != HttpURLConnection.HTTP_PRECON_FAILED &&
response.statusCode() != 416)) ||
/* 416 is missing from the Enum but it is Range Not Satisfiable */
(response.statusCode() >= 500 && response.statusCode() <= 509)) {
String errorString = String.format(Locale.ROOT,
"REQUEST ERROR%nHTTP request failed with status code:'%d'%n",
response.statusCode());
if (currentLevel == HttpPipelineLogLevel.WARNING) {
logMessage += errorString;
} else {
logMessage = errorString;
}
currentLevel = HttpPipelineLogLevel.ERROR;
}
/*
We don't want to format the log message unless we have to. Format once we've determined that
either the customer wants this log level or we need to force log it.
*/
if (this.shouldLog(currentLevel)) {
String additionalMessageInfo = buildAdditionalMessageInfo(request);
String messageInfo = String.format(Locale.ROOT,
"Request try:'%d', request duration:'%d' ms, operation duration:'%d' ms%n%s",
tryCount, requestCompletionTime, operationDuration, additionalMessageInfo);
this.log(currentLevel, logMessage + messageInfo);
}
});
}
private String buildAdditionalMessageInfo(final HttpRequest httpRequest) {
HttpRequest sanitizedRequest = buildSanitizedRequest(httpRequest);
StringBuilder stringBuilder = new StringBuilder();
String format = "%s: %s" + System.lineSeparator();
stringBuilder.append(String.format(format, sanitizedRequest.httpMethod().toString(),
sanitizedRequest.url().toString()));
sanitizedRequest.headers().forEach((header) -> stringBuilder.append(String.format(format, header.name(),
header.value())));
return stringBuilder.toString();
}
private HttpRequest buildSanitizedRequest(final HttpRequest initialRequest) {
// Build new URL and redact SAS signature, if present
URL url = sanitizeURL(initialRequest.url());
// Build resultRequest
HttpRequest resultRequest = new HttpRequest(
initialRequest.callerMethod(),
initialRequest.httpMethod(),
url,
initialRequest.headers(),
initialRequest.body(),
initialRequest.responseDecoder());
// Redact Authorization header, if present
if (resultRequest.headers().value(Constants.HeaderConstants.AUTHORIZATION) != null) {
resultRequest.headers().set(Constants.HeaderConstants.AUTHORIZATION, Constants.REDACTED);
}
// Redact Copy Source header SAS signature, if present
if (resultRequest.headers().value(Constants.HeaderConstants.COPY_SOURCE) != null) {
try {
URL copySourceUrl = sanitizeURL(new URL(resultRequest.headers()
.value(Constants.HeaderConstants.COPY_SOURCE)));
resultRequest.headers().set(Constants.HeaderConstants.COPY_SOURCE, copySourceUrl.toString());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
return resultRequest;
}
private URL sanitizeURL(URL initialURL) {
URL resultURL = initialURL;
try {
BlobURLParts urlParts = URLParser.parse(initialURL);
if (urlParts.sasQueryParameters() == null || urlParts.sasQueryParameters().signature() == null) {
return resultURL;
}
urlParts.withSasQueryParameters(new SASQueryParameters(
urlParts.sasQueryParameters().version(),
urlParts.sasQueryParameters().services(),
urlParts.sasQueryParameters().resourceTypes(),
urlParts.sasQueryParameters().protocol(),
urlParts.sasQueryParameters().startTime(),
urlParts.sasQueryParameters().expiryTime(),
urlParts.sasQueryParameters().ipRange(),
urlParts.sasQueryParameters().identifier(),
urlParts.sasQueryParameters().resource(),
urlParts.sasQueryParameters().permissions(),
Constants.REDACTED,
urlParts.sasQueryParameters().cacheControl(),
urlParts.sasQueryParameters().contentDisposition(),
urlParts.sasQueryParameters().contentEncoding(),
urlParts.sasQueryParameters().contentLanguage(),
urlParts.sasQueryParameters().contentType()
));
resultURL = urlParts.toURL();
/*
We are only making valid changes to what has already been validated as a URL (since we got it from a
URL object), so there should be no need for either us or the caller to check this error.
*/
} catch (UnknownHostException | MalformedURLException e) {
throw new RuntimeException(e);
}
return resultURL;
}
/*
We need to support the HttpPipelineLogger as it already exists. We also want to allow users to hook up SLF4J.
Finally, we need to do our own default logging.
*/
private void log(HttpPipelineLogLevel level, String message) {
/*
We need to explicitly check before we send it to the HttpPipelineLogger as its log function may only
expect to receive messages for which shouldLog() returns true.
*/
if (this.options.shouldLog(level)) {
this.options.log(level, message);
}
/*
The Java logger and slf4j logger should do the correct thing given any log level. forceLogger is
configured to only log warnings and errors.
*/
if (!this.factory.loggingOptions.disableDefaultLogging() && LoggingFactory.defaultLoggerLoaded) {
forceLogger.log(javaLogLevelMap.get(level), message);
}
if (level.equals(HttpPipelineLogLevel.ERROR)) {
slf4jLogger.error(message);
} else if (level.equals(HttpPipelineLogLevel.WARNING)) {
slf4jLogger.warn(message);
} else if (level.equals(HttpPipelineLogLevel.INFO)) {
slf4jLogger.info(message);
}
}
/*
Check the HttpPipelineLogger, SLF4J Logger, and Java Logger
*/
private boolean shouldLog(HttpPipelineLogLevel level) {
// Default log Warnings and Errors as long as default logging is enabled.
if ((level.equals(HttpPipelineLogLevel.WARNING) || level.equals(HttpPipelineLogLevel.ERROR)) &&
!this.factory.loggingOptions.disableDefaultLogging() && LoggingFactory.defaultLoggerLoaded) {
return true;
}
// The user has configured the HttpPipelineLogger to log at this level.
if (this.options.shouldLog(level)) {
return true;
}
// The SLF4J logger is configured at the given level.
if ((level.equals(HttpPipelineLogLevel.INFO) && slf4jLogger.isInfoEnabled()) ||
(level.equals(HttpPipelineLogLevel.WARNING) && slf4jLogger.isWarnEnabled()) ||
(level.equals(HttpPipelineLogLevel.ERROR) && slf4jLogger.isErrorEnabled())) {
return true;
}
return false;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy