
com.google.appengine.api.log.LogQuery Maven / Gradle / Ivy
/*
* Copyright 2021 Google LLC
*
* 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
*
* https://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.appengine.api.log;
import com.google.appengine.api.internal.ImmutableCopy;
import com.google.appengine.api.internal.Repackaged;
import com.google.appengine.api.log.LogService.LogLevel;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Allows users to customize the behavior of {@link LogService#fetch(LogQuery)}.
*
* {@code startTime} is the earliest request completion or last-update time
* that results should be fetched for, in seconds since the Unix epoch. If
* {@code null} then no requests will be excluded for ending too long ago.
*
* {@code endTime} is the latest request completion or last-update time that
* results should be fetched for, in seconds since the Unix epoch. If
* {@code null} then no requests will be excluded for ending too recently.
*
* {@code offset} is a cursor into the log stream retrieved from a previously
* emitted {@link RequestLogs#getOffset}. This iterator will begin returning
* logs immediately after the record from which the offset came. If
* {@code null}, the query will begin at {@code startTime}.
*
* {@code minLogLevel} is a {@link LogService.LogLevel} which serves as a
* filter on the requests returned. Requests with no application logs at or
* above the specified level will be omitted. Works even if
* {@code includeAppLogs} is not True.
*
* {@code includeIncomplete} selects whether requests that have started but not
* yet finished should be included in the query. Defaults to False.
*
* {@code includeAppLogs} specifies whether or not to include application logs
* in the query results. Defaults to False.
*
* {@code majorVersionIds} specifies versions of the application's default
* module for which logs records should retrieved.
*
* {@code versions} specifies module versions of the application for which
* logs should be retrieved.
*
* {@code requestIds}, if not {@code empty()}, indicates that instead of a
* time-based scan, logs for the specified requests should be returned.
* See the Request IDs section of
* the Java Servlet Environment documentation for how to retrieve these IDs
* at runtime. Malformed request IDs cause an exception and unrecognized request IDs
* are ignored. This option may not be combined with other filtering options such as
* startTime, endTime, offset, or minLogLevel. When {@code requestIds} is not {@code empty()},
* {@code majorVersionIds} are ignored. Logs are returned in the order requested.
*
* {@code batchSize} specifies the internal batching strategy of the returned
* {@link java.lang.Iterable Iterable<RequestLogs>}. Has no impact on the
* result of the query.
*
* Notes on usage:
* The recommended way to instantiate a {@code LogQuery} object is to
* statically import {@link Builder}.* and invoke a static
* creation method followed by an instance mutator (if needed):
*
*
* import static com.google.appengine.api.log.LogQuery.Builder.*;
*
* ...
*
* // All requests, including application logs.
* iter = logService.fetch(withIncludeAppLogs(true));
*
* // All requests ending in the past day (or still running) with an info log or higher.
* Calendar cal = Calendar.getInstance();
* cal.add(Calendar.DAY_OF_MONTH, -1);
* iter = logService.fetch(withEndTimeMillis(cal.time())
* .includeIncomplete(true).minimumLogLevel(LogService.INFO));
*
*
* There are a couple of ways to configure {@link LogQuery} to limit
* {@link LogService#fetch(LogQuery)} to only return log records for specific
* module versions.
*
* - {@link #versions(List)}({@link Builder#withVersions(List)}) -
* Includes designated module versions for the application.
*
- {@link #majorVersionIds(List)} ({@link Builder#withMajorVersionIds(List)}) -
* Includes designated versions of the default module for the application.
*
* For a particular {@link LogQuery} only one of these methods may be used. If neither is used,
* {@link LogService#fetch(LogQuery)} results may include any module version.
*
* It is not allowed to call both {@link #versions(List)} ({@link Builder#withVersions(List)})
* and {@link #requestIds(List)}({@link Builder#withRequestIds(List)} for the same {@link LogQuery}.
*
*/
public final class LogQuery implements Cloneable, Serializable {
private static final long serialVersionUID = 3660093076203855168L;
static final Comparator VERSION_COMPARATOR = new VersionComparator();
// The offset here is a String representation of the next location in the
// logs stream where results should be returned from.
@Nullable private String offset;
@Nullable private Long startTimeUsec;
@Nullable private Long endTimeUsec;
private int batchSize = LogService.DEFAULT_ITEMS_PER_FETCH;
@Nullable private LogLevel minLogLevel;
private boolean includeIncomplete = false;
private boolean includeAppLogs = false;
private List majorVersionIds = new ArrayList();
private List versions = new ArrayList();
private List requestIds = new ArrayList();
private static final String MAJOR_VERSION_ID_REGEX = "[a-z\\d][a-z\\d\\-]{0,99}";
private static final Pattern VERSION_PATTERN = Pattern.compile(MAJOR_VERSION_ID_REGEX);
private static final String REQUEST_ID_REGEX = "\\A\\p{XDigit}+\\z";
private static final Pattern REQUEST_ID_PATTERN = Pattern.compile(REQUEST_ID_REGEX);
/**
* Specifies a version of a module.
*/
public static final class Version implements Serializable {
private static final long serialVersionUID = 3697597908142270764L;
private static final String INVALID_MODULE_ID_MESSAGE_TEMPLATE = "Invalid module id '%s'";
private static final String INVALID_VERSION_ID_MESSAGE_TEMPLATE = "Invalid version id '%s'";
private final String moduleId;
private final String versionId;
/**
* Constructs a {@link Version}.
* @param moduleId A valid module id.
* @param versionId A valid version id.
*/
public Version(String moduleId, String versionId) {
this.moduleId = verifyId(moduleId, INVALID_MODULE_ID_MESSAGE_TEMPLATE);
this.versionId = verifyId(versionId, INVALID_VERSION_ID_MESSAGE_TEMPLATE);
}
/**
* Returns the moduleId.
*/
public String getModuleId() {
return moduleId;
}
/**
* Returns the version id.
*/
public String getVersionId() {
return versionId;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((moduleId == null) ? 0 : moduleId.hashCode());
result = prime * result + ((versionId == null) ? 0 : versionId.hashCode());
return result;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Version other = (Version) obj;
if (moduleId == null) {
if (other.moduleId != null) {
return false;
}
} else if (!moduleId.equals(other.moduleId)) {
return false;
}
if (versionId == null) {
if (other.versionId != null) {
return false;
}
} else if (!versionId.equals(other.versionId)) {
return false;
}
return true;
}
@Override
public String toString() {
return "Version: moduleId=" + moduleId + " versionId=" + versionId;
}
/**
* Verifies and returns a module id or version id.
*
* Note this verification uses the VERSION_PATTERN regular expression which will catch
* many but not all syntactic errors.
*
* @param moduleOrVersionId The module or version id to verify.
* @param messageTemplate A message template with a placeholder for the invalid value.
* @return moduleOrVersionId
* @throws IllegalArgumentException if moduleOrVersionId is not valid.
*/
private static String verifyId(String moduleOrVersionId, String messageTemplate) {
Matcher matcher = VERSION_PATTERN.matcher(moduleOrVersionId);
if (!matcher.matches()) {
throw new IllegalArgumentException(String.format(messageTemplate, moduleOrVersionId));
}
return moduleOrVersionId;
}
}
/**
* Compares {@link Version} values by {@link Version#getModuleId()} and
* breaks ties by comparing by {@link Version#getVersionId()}
*/
private static class VersionComparator implements Comparator {
@Override
public int compare(Version version1, Version version2) {
int result = version1.getModuleId().compareTo(version2.getModuleId());
if (result == 0) {
result = version1.getVersionId().compareTo(version2.getVersionId());
}
return result;
}
}
/**
* Contains static creation methods for {@link LogQuery}.
*/
public static final class Builder {
/**
* Create a {@link LogQuery} with the given offset.
* Shorthand for LogQuery.Builder.withDefaults().offset(offset);
.
* Please read the {@link LogQuery} class javadoc for an explanation of
* how offsets are used.
* @param offset the offset to use.
* @return The newly created LogQuery instance.
*/
public static LogQuery withOffset(String offset) {
return withDefaults().offset(offset);
}
/**
* Create a {@link LogQuery} with the given start time.
* Shorthand for LogQuery.Builder.withDefaults().startTimeMillis(startTimeMillis);
.
* Please read the {@link LogQuery} class javadoc for an explanation of
* how start time is used.
* @param startTimeMillis the start time to use, in milliseconds.
* @return The newly created LogQuery instance.
*/
public static LogQuery withStartTimeMillis(long startTimeMillis) {
return withDefaults().startTimeMillis(startTimeMillis);
}
/**
* Create a {@link LogQuery} with the given start time.
* Shorthand for LogQuery.Builder.withDefaults().startTimeUsec(startTimeUsec);
.
* Please read the {@link LogQuery} class javadoc for an explanation of
* how start time is used.
* @param startTimeUsec the start time to use, in microseconds.
* @return The newly created LogQuery instance.
*/
public static LogQuery withStartTimeUsec(long startTimeUsec) {
return withDefaults().startTimeUsec(startTimeUsec);
}
/**
* Create a {@link LogQuery} with the given end time.
* Shorthand for LogQuery.Builder.withDefaults().endTimeMillis(endTimeMillis);
.
* Please read the {@link LogQuery} class javadoc for an explanation of
* how end time is used.
* @param endTimeMillis the start time to use, in milliseconds.
* @return The newly created LogQuery instance.
*/
public static LogQuery withEndTimeMillis(long endTimeMillis) {
return withDefaults().endTimeMillis(endTimeMillis);
}
/**
* Create a {@link LogQuery} with the given end time.
* Shorthand for LogQuery.Builder.withDefaults().endTimeUsec(endTimeUsec);
.
* Please read the {@link LogQuery} class javadoc for an explanation of
* how end time is used.
* @param endTimeUsec the start time to use, in microseconds.
* @return The newly created LogQuery instance.
*/
public static LogQuery withEndTimeUsec(long endTimeUsec) {
return withDefaults().endTimeUsec(endTimeUsec);
}
/**
* Create a {@link LogQuery} with the given batch size.
* Shorthand for LogQuery.Builder.withDefaults().batchSize(batchSize);
.
* Please read the {@link LogQuery} class javadoc for an explanation of
* how batch size is used.
* @param batchSize the batch size to set.
* @return The newly created LogQuery instance.
*/
public static LogQuery withBatchSize(int batchSize) {
return withDefaults().batchSize(batchSize);
}
/**
* Create a {@link LogQuery} with the given minimum log level.
* Shorthand for LogQuery.Builder.withDefaults().minLogLevel(minLogLevel);
.
* Please read the {@link LogQuery} class javadoc for an explanation of
* how minimum log level is used.
* @param minLogLevel the minimum log level to set.
* @return The newly created LogQuery instance.
*/
public static LogQuery withMinLogLevel(LogLevel minLogLevel) {
return withDefaults().minLogLevel(minLogLevel);
}
/**
* Create a {@link LogQuery} with the given include incomplete setting.
* Shorthand for
* LogQuery.Builder.withDefaults().includeIncomplete(includeIncomplete);
.
* Please read the {@link LogQuery} class javadoc for an explanation of
* how include incomplete is used.
* @param includeIncomplete the inclusion value to set.
* @return The newly created LogQuery instance.
*/
public static LogQuery withIncludeIncomplete(boolean includeIncomplete) {
return withDefaults().includeIncomplete(includeIncomplete);
}
/**
* Create a {@link LogQuery} with include application logs set.
* Shorthand for LogQuery.Builder.withDefaults().includeAppLogs(includeAppLogs);
.
* Please read the {@link LogQuery} class javadoc for an explanation of
* the include application logs setting.
* @param includeAppLogs the inclusion value to set.
* @return The newly created LogQuery instance.
*/
public static LogQuery withIncludeAppLogs(boolean includeAppLogs) {
return withDefaults().includeAppLogs(includeAppLogs);
}
/**
* Create a {@link LogQuery} with the given major version IDs.
* Shorthand for LogQuery.Builder.withDefaults().majorVersionIds(versionIds);
.
* Please read the {@link LogQuery} class javadoc for an explanation of
* how the list of major version ids is used.
* @param versionIds the major version id list to set.
* @return The newly created LogQuery instance.
*/
public static LogQuery withMajorVersionIds(List versionIds) {
return withDefaults().majorVersionIds(versionIds);
}
/**
* Create a {@link LogQuery} with the given {@link Version} values.
* Shorthand for
* LogQuery.Builder.withDefaults().versions(versions);
.
* Please read the {@link LogQuery} class javadoc for usage information.
* @param versions the list to set.
* @return The newly created LogQuery instance.
*/
public static LogQuery withVersions(List versions) {
return withDefaults().versions(versions);
}
/**
* Create a {@link LogQuery} with the given request IDs.
* Shorthand for LogQuery.Builder.withDefaults().requestIds(requestIds);
.
* See the {@link LogQuery} class javadoc for an explanation of
* how the list of request ids is used.
* @param requestIds the request id list to set.
* @return The newly created LogQuery instance.
* @since App Engine 1.7.4.
*/
public static LogQuery withRequestIds(List requestIds) {
return withDefaults().requestIds(requestIds);
}
/**
* Helper method for creating a {@link LogQuery} instance with
* default values. Please read the {@link LogQuery} class javadoc for an
* explanation of the defaults.
*/
public static LogQuery withDefaults() {
return new LogQuery();
}
}
/**
* Makes a copy of a provided LogQuery.
*
* @return A new LogQuery whose fields are copied from the given LogQuery.
*/
@Override
public LogQuery clone() {
LogQuery clone;
try {
clone = (LogQuery) super.clone();
} catch (CloneNotSupportedException e) {
// This shouldn't happen - just catching to avoid making callers catch it.
throw new RuntimeException(e);
}
clone.majorVersionIds = new ArrayList(majorVersionIds);
clone.requestIds = new ArrayList(requestIds);
clone.versions = new ArrayList(versions);
return clone;
}
/**
* Sets the offset. Please read the class javadoc for an explanation of
* how offset is used.
* @param offset The offset to set.
* @return {@code this} (for chaining)
*/
public LogQuery offset(String offset) {
this.offset = offset;
return this;
}
/**
* Sets the start time to a value in milliseconds. Please read the class
* javadoc for an explanation of how start time is used.
* @param startTimeMillis The start time to set, in milliseconds.
* @return {@code this} (for chaining)
*/
public LogQuery startTimeMillis(long startTimeMillis) {
this.startTimeUsec = startTimeMillis * 1000;
return this;
}
/**
* Sets the start time to a value in microseconds. Please read the class
* javadoc for an explanation of how start time is used.
* @param startTimeUsec The start time to set, in microseconds.
* @return {@code this} (for chaining)
*/
public LogQuery startTimeUsec(long startTimeUsec) {
this.startTimeUsec = startTimeUsec;
return this;
}
/**
* Sets the end time to a value in milliseconds. Please read the class
* javadoc for an explanation of how end time is used.
* @param endTimeMillis The end time to set, in milliseconds.
* @return {@code this} (for chaining)
*/
public LogQuery endTimeMillis(long endTimeMillis) {
this.endTimeUsec = endTimeMillis * 1000;
return this;
}
/**
* Sets the end time to a value in microseconds. Please read the class
* javadoc for an explanation of how end time is used.
* @param endTimeUsec The end time to set, in microseconds.
* @return {@code this} (for chaining)
*/
public LogQuery endTimeUsec(long endTimeUsec) {
this.endTimeUsec = endTimeUsec;
return this;
}
/**
* Sets the batch size. Please read the class javadoc for an explanation of
* how batch size is used.
* @param batchSize The batch size to set. Must be greater than 0.
* @return {@code this} (for chaining)
*/
public LogQuery batchSize(int batchSize) {
if (batchSize < 1) {
throw new IllegalArgumentException("batchSize must be greater than zero");
}
this.batchSize = batchSize;
return this;
}
/**
* Sets the minimum log level. Please read the class javadoc for an
* explanation of how minimum log level is used.
* @param minLogLevel The minimum log level to set.
* @return {@code this} (for chaining)
*/
public LogQuery minLogLevel(LogLevel minLogLevel) {
this.minLogLevel = minLogLevel;
return this;
}
/**
* Sets include incomplete. Please read the class javadoc for an
* explanation of how include incomplete is used.
* @param includeIncomplete The value to set.
* @return {@code this} (for chaining)
*/
public LogQuery includeIncomplete(boolean includeIncomplete) {
this.includeIncomplete = includeIncomplete;
return this;
}
/**
* Sets include application logs. Please read the class javadoc for an
* explanation of how include application logs is used.
* @param includeAppLogs The value to set.
* @return {@code this} (for chaining)
*/
public LogQuery includeAppLogs(boolean includeAppLogs) {
this.includeAppLogs = includeAppLogs;
return this;
}
/**
* Sets the major version identifiers to query. Please read the class
* javadoc for an explanation of how major versions are used.
* @param versionIds The major version identifier list to set.
* @return {@code this} (for chaining)
*/
public LogQuery majorVersionIds(List versionIds) {
if (!versions.isEmpty()) {
throw new IllegalStateException(
"LogQuery.majorVersionIds may not be called after LogQuery.versions.");
}
for (String versionId : versionIds) {
Matcher matcher = VERSION_PATTERN.matcher(versionId);
if (!matcher.matches()) {
throw new IllegalArgumentException(
"versionIds must only contain valid "
+ "major version identifiers. Version "
+ versionId
+ " is not a valid "
+ "major version identifier.");
}
}
this.majorVersionIds = Repackaged.copyIfRepackagedElseOriginal(versionIds);
return this;
}
/**
* Restricts the query to log records for the specified module versions.
*
* Please read the class javadoc for usage information.
*
* @param versions The list of module versions to query.
* @return {@code this} (for chaining)
*/
public LogQuery versions(List versions) {
if (!this.majorVersionIds.isEmpty()) {
throw new IllegalStateException(
"LogQuery.versions may not be called after LogQuery.majorVersionIds.");
}
if (!requestIds.isEmpty()) {
throw new IllegalStateException(
"LogQuery.versions may not be called after LogQuery.requestIds.");
}
this.versions.clear();
this.versions.addAll(versions);
return this;
}
/**
* Sets the list of request ids to query. See the class javadoc for an
* explanation of how request ids are used.
* @param requestIds The request id list to set.
* @return {@code this} (for chaining)
*/
public LogQuery requestIds(List requestIds) {
if (!versions.isEmpty()) {
throw new IllegalStateException(
"LogQuery.requestIds may not be called after LogQuery.versions.");
}
Set seen = new HashSet();
for (String requestId : requestIds) {
if (!seen.add(requestId)) {
throw new IllegalArgumentException("requestIds must be unique.");
}
Matcher matcher = REQUEST_ID_PATTERN.matcher(requestId);
if (!matcher.matches()) {
throw new IllegalArgumentException(
"requestIds must only contain valid "
+ "request ids. "
+ requestId
+ " is not a valid request id.");
}
}
this.requestIds = Repackaged.copyIfRepackagedElseOriginal(requestIds);
return this;
}
/**
* @return The offset, or {@code null} if none was provided.
*/
public @Nullable String getOffset() {
return offset;
}
/**
* @return The batch size, or {@code null} if none was provided.
*/
public @Nullable Integer getBatchSize() {
return batchSize;
}
/**
* @return The end time in milliseconds, or {@code null} if none was provided.
*/
public @Nullable Long getEndTimeMillis() {
return endTimeUsec != null ? endTimeUsec / 1000 : null;
}
/**
* @return The end time in microseconds, or {@code null} if none was provided.
*/
public @Nullable Long getEndTimeUsec() {
return endTimeUsec;
}
/**
* @return Whether or not application logs should be returned.
*/
public Boolean getIncludeAppLogs() {
return includeAppLogs;
}
/**
* @return Whether or not incomplete request logs should be returned.
*/
public Boolean getIncludeIncomplete() {
return includeIncomplete;
}
/**
* @return The minimum log level, or {@code null} if none was provided.
*/
public @Nullable LogLevel getMinLogLevel() {
return minLogLevel;
}
/**
* @return The start time in milliseconds, or {@code null} if none was provided.
*/
public @Nullable Long getStartTimeMillis() {
return startTimeUsec != null ? startTimeUsec / 1000 : null;
}
/**
* @return The start time in microseconds, or {@code null} if none was provided.
*/
public @Nullable Long getStartTimeUsec() {
return startTimeUsec;
}
/**
* @return The list of major app versions that should be queried over, or
* an empty list if none were set.
*/
public List getMajorVersionIds() {
return majorVersionIds;
}
/**
* @return The list possibly empty list of module versions that should be queried over.
*/
public List getVersions() {
return ImmutableCopy.list(versions);
}
/**
* @return The list of request ids that should be queried over, or
* {@code null} if none were set.
*/
public List getRequestIds() {
return requestIds;
}
}