Skip to content

Commit

Permalink
Implement new Debug API with redaction.
Browse files Browse the repository at this point in the history
Implement emittingSingleLine TextFormat printer option.

PiperOrigin-RevId: 633672722
  • Loading branch information
protobuf-github-bot authored and copybara-github committed May 14, 2024
1 parent 5a91707 commit bb68eb2
Show file tree
Hide file tree
Showing 5 changed files with 371 additions and 19 deletions.
1 change: 1 addition & 0 deletions java/core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ LITE_TEST_EXCLUSIONS = [
"src/test/java/com/google/protobuf/AnyTest.java",
"src/test/java/com/google/protobuf/CodedInputStreamTest.java",
"src/test/java/com/google/protobuf/DeprecatedFieldTest.java",
"src/test/java/com/google/protobuf/DebugFormatTest.java",
"src/test/java/com/google/protobuf/DescriptorsTest.java",
"src/test/java/com/google/protobuf/DiscardUnknownFieldsTest.java",
"src/test/java/com/google/protobuf/DynamicMessageTest.java",
Expand Down
79 changes: 79 additions & 0 deletions java/core/src/main/java/com/google/protobuf/DebugFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.google.protobuf;

import com.google.protobuf.Descriptors.FieldDescriptor;

/**
* Provides an explicit API for unstable, redacting debug output suitable for debug logging. This
* implementation is based on TextFormat, but should not be parsed.
*/
public final class DebugFormat {

private final boolean isSingleLine;

private DebugFormat(boolean singleLine) {
isSingleLine = singleLine;
}

public static DebugFormat singleLine() {
return new DebugFormat(true);
}

public static DebugFormat multiline() {
return new DebugFormat(false);
}

public String toString(MessageOrBuilder message) {
return TextFormat.printer()
.emittingSingleLine(this.isSingleLine)
.enablingSafeDebugFormat(true)
.printToString(message);
}

public String toString(FieldDescriptor field, Object value) {
return TextFormat.printer()
.emittingSingleLine(this.isSingleLine)
.enablingSafeDebugFormat(true)
.printFieldToString(field, value);
}

public String toString(UnknownFieldSet fields) {
return TextFormat.printer()
.emittingSingleLine(this.isSingleLine)
.enablingSafeDebugFormat(true)
.printToString(fields);
}

public Object lazyToString(MessageOrBuilder message) {
return new LazyDebugOutput(message, this);
}

public Object lazyToString(UnknownFieldSet fields) {
return new LazyDebugOutput(fields, this);
}

private static class LazyDebugOutput {
private final MessageOrBuilder message;
private final UnknownFieldSet fields;
private final DebugFormat format;

LazyDebugOutput(MessageOrBuilder message, DebugFormat format) {
this.message = message;
this.fields = null;
this.format = format;
}

LazyDebugOutput(UnknownFieldSet fields, DebugFormat format) {
this.message = null;
this.fields = fields;
this.format = format;
}

@Override
public String toString() {
if (message != null) {
return format.toString(message);
}
return format.toString(fields);
}
}
}
146 changes: 127 additions & 19 deletions java/core/src/main/java/com/google/protobuf/TextFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ private TextFormat() {}

private static final String DEBUG_STRING_SILENT_MARKER = "\t ";

private static final String REDACTED_MARKER = "[REDACTED]";

/**
* Generates a human readable form of this message, useful for debugging and other purposes, with
* no newline characters. This is just a trivial wrapper around {@link
Expand All @@ -58,7 +60,7 @@ public static String shortDebugString(final MessageOrBuilder message) {
*/
public static void printUnknownFieldValue(
final int tag, final Object value, final Appendable output) throws IOException {
printUnknownFieldValue(tag, value, multiLineOutput(output));
printUnknownFieldValue(tag, value, setSingleLineOutput(output, false));
}

private static void printUnknownFieldValue(
Expand Down Expand Up @@ -109,21 +111,37 @@ public static final class Printer {
// Printer instance which escapes non-ASCII characters.
private static final Printer DEFAULT =
new Printer(
true, TypeRegistry.getEmptyTypeRegistry(), ExtensionRegistryLite.getEmptyRegistry());
true,
TypeRegistry.getEmptyTypeRegistry(),
ExtensionRegistryLite.getEmptyRegistry(),
false,
false);

/** Whether to escape non ASCII characters with backslash and octal. */
private final boolean escapeNonAscii;

private final TypeRegistry typeRegistry;
private final ExtensionRegistryLite extensionRegistry;

/**
* Whether to enable redaction of sensitive fields and introduce randomization. Note that when
* this is enabled, the output will no longer be deserializable.
*/
private final boolean enablingSafeDebugFormat;

private final boolean singleLine;

private Printer(
boolean escapeNonAscii,
TypeRegistry typeRegistry,
ExtensionRegistryLite extensionRegistry) {
ExtensionRegistryLite extensionRegistry,
boolean enablingSafeDebugFormat,
boolean singleLine) {
this.escapeNonAscii = escapeNonAscii;
this.typeRegistry = typeRegistry;
this.extensionRegistry = extensionRegistry;
this.enablingSafeDebugFormat = enablingSafeDebugFormat;
this.singleLine = singleLine;
}

/**
Expand All @@ -136,7 +154,8 @@ private Printer(
* with the escape mode set to the given parameter.
*/
public Printer escapingNonAscii(boolean escapeNonAscii) {
return new Printer(escapeNonAscii, typeRegistry, extensionRegistry);
return new Printer(
escapeNonAscii, typeRegistry, extensionRegistry, enablingSafeDebugFormat, singleLine);
}

/**
Expand All @@ -149,7 +168,8 @@ public Printer usingTypeRegistry(TypeRegistry typeRegistry) {
if (this.typeRegistry != TypeRegistry.getEmptyTypeRegistry()) {
throw new IllegalArgumentException("Only one typeRegistry is allowed.");
}
return new Printer(escapeNonAscii, typeRegistry, extensionRegistry);
return new Printer(
escapeNonAscii, typeRegistry, extensionRegistry, enablingSafeDebugFormat, singleLine);
}

/**
Expand All @@ -162,7 +182,34 @@ public Printer usingExtensionRegistry(ExtensionRegistryLite extensionRegistry) {
if (this.extensionRegistry != ExtensionRegistryLite.getEmptyRegistry()) {
throw new IllegalArgumentException("Only one extensionRegistry is allowed.");
}
return new Printer(escapeNonAscii, typeRegistry, extensionRegistry);
return new Printer(
escapeNonAscii, typeRegistry, extensionRegistry, enablingSafeDebugFormat, singleLine);
}

/**
* Return a new Printer instance that outputs a redacted and unstable format suitable for
* debugging.
*
* @param enablingSafeDebugFormat If true, the new Printer will redact all proto fields that are
* marked by a debug_redact=true option, and apply an unstable prefix to the output.
* @return a new Printer that clones all other configurations from the current {@link Printer},
* with the enablingSafeDebugFormat mode set to the given parameter.
*/
Printer enablingSafeDebugFormat(boolean enablingSafeDebugFormat) {
return new Printer(
escapeNonAscii, typeRegistry, extensionRegistry, enablingSafeDebugFormat, singleLine);
}

/**
* Return a new Printer instance with the specified line formatting status.
*
* @param singleLine If true, the new Printer will output no newline characters.
* @return a new Printer that clones all other configurations from the current {@link Printer},
* with the singleLine mode set to the given parameter.
*/
public Printer emittingSingleLine(boolean singleLine) {
return new Printer(
escapeNonAscii, typeRegistry, extensionRegistry, enablingSafeDebugFormat, singleLine);
}

/**
Expand All @@ -171,12 +218,12 @@ public Printer usingExtensionRegistry(ExtensionRegistryLite extensionRegistry) {
* original Protocol Buffer system)
*/
public void print(final MessageOrBuilder message, final Appendable output) throws IOException {
print(message, multiLineOutput(output));
print(message, setSingleLineOutput(output, this.singleLine));
}

/** Outputs a textual representation of {@code fields} to {@code output}. */
public void print(final UnknownFieldSet fields, final Appendable output) throws IOException {
printUnknownFields(fields, multiLineOutput(output));
printUnknownFields(fields, setSingleLineOutput(output, this.singleLine));
}

private void print(final MessageOrBuilder message, final TextGenerator generator)
Expand All @@ -188,6 +235,14 @@ && printAny(message, generator)) {
printMessage(message, generator);
}

private void applyUnstablePrefix(final Appendable output) {
try {
output.append("");
} catch (IOException e) {
throw new IllegalStateException(e);
}
}

/**
* Attempt to print the 'google.protobuf.Any' message in a human-friendly format. Returns false
* if the message isn't a valid 'google.protobuf.Any' message (in which case the message should
Expand Down Expand Up @@ -244,6 +299,9 @@ private boolean printAny(final MessageOrBuilder message, final TextGenerator gen
public String printFieldToString(final FieldDescriptor field, final Object value) {
try {
final StringBuilder text = new StringBuilder();
if (enablingSafeDebugFormat) {
applyUnstablePrefix(text);
}
printField(field, value, text);
return text.toString();
} catch (IOException e) {
Expand All @@ -253,7 +311,7 @@ public String printFieldToString(final FieldDescriptor field, final Object value

public void printField(final FieldDescriptor field, final Object value, final Appendable output)
throws IOException {
printField(field, value, multiLineOutput(output));
printField(field, value, setSingleLineOutput(output, this.singleLine));
}

private void printField(
Expand Down Expand Up @@ -358,12 +416,19 @@ public int compareTo(MapEntryAdapter b) {
public void printFieldValue(
final FieldDescriptor field, final Object value, final Appendable output)
throws IOException {
printFieldValue(field, value, multiLineOutput(output));
printFieldValue(field, value, setSingleLineOutput(output, this.singleLine));
}

private void printFieldValue(
final FieldDescriptor field, final Object value, final TextGenerator generator)
throws IOException {
if (shouldRedact(field)) {
generator.print(REDACTED_MARKER);
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
generator.eol();
}
return;
}
switch (field.getType()) {
case INT32:
case SINT32:
Expand Down Expand Up @@ -429,10 +494,54 @@ private void printFieldValue(
}
}

private boolean shouldRedactOptionValue(EnumValueDescriptor optionValue) {
if (optionValue.getOptions().hasDebugRedact()) {
return optionValue.getOptions().getDebugRedact();
}
return false;
}

// The criteria for redacting a field is as follows: 1) The enablingSafeDebugFormat printer
// option
// must be on. 2) The field must be marked by a debug_redact=true option, or is marked by an
// option with an enum value that is marked by a debug_redact=true option.
private boolean shouldRedact(final FieldDescriptor field) {
if (!this.enablingSafeDebugFormat) {
return false;
}
if (field.getOptions().hasDebugRedact()) {
return field.getOptions().getDebugRedact();
}
// Iterate through every option; if it's an enum, we check each enum value for debug_redact.
for (Map.Entry<Descriptors.FieldDescriptor, Object> entry :
field.getOptions().getAllFields().entrySet()) {
Descriptors.FieldDescriptor option = entry.getKey();
if (option.getType() != Descriptors.FieldDescriptor.Type.ENUM) {
continue;
}
if (option.isRepeated()) {
for (EnumValueDescriptor value : (List<EnumValueDescriptor>) entry.getValue()) {
if (shouldRedactOptionValue(value)) {
return true;
}
}
} else {
EnumValueDescriptor optionValue = (EnumValueDescriptor) entry.getValue();
if (shouldRedactOptionValue(optionValue)) {
return true;
}
}
}
return false;
}

/** Like {@code print()}, but writes directly to a {@code String} and returns it. */
public String printToString(final MessageOrBuilder message) {
try {
final StringBuilder text = new StringBuilder();
if (enablingSafeDebugFormat) {
applyUnstablePrefix(text);
}
print(message, text);
return text.toString();
} catch (IOException e) {
Expand All @@ -443,6 +552,9 @@ public String printToString(final MessageOrBuilder message) {
public String printToString(final UnknownFieldSet fields) {
try {
final StringBuilder text = new StringBuilder();
if (enablingSafeDebugFormat) {
applyUnstablePrefix(text);
}
print(fields, text);
return text.toString();
} catch (IOException e) {
Expand All @@ -457,7 +569,7 @@ public String printToString(final UnknownFieldSet fields) {
public String shortDebugString(final MessageOrBuilder message) {
try {
final StringBuilder text = new StringBuilder();
print(message, singleLineOutput(text));
print(message, setSingleLineOutput(text, true));
return text.toString();
} catch (IOException e) {
throw new IllegalStateException(e);
Expand All @@ -471,7 +583,7 @@ public String shortDebugString(final MessageOrBuilder message) {
public String shortDebugString(final FieldDescriptor field, final Object value) {
try {
final StringBuilder text = new StringBuilder();
printField(field, value, singleLineOutput(text));
printField(field, value, setSingleLineOutput(text, true));
return text.toString();
} catch (IOException e) {
throw new IllegalStateException(e);
Expand All @@ -485,7 +597,7 @@ public String shortDebugString(final FieldDescriptor field, final Object value)
public String shortDebugString(final UnknownFieldSet fields) {
try {
final StringBuilder text = new StringBuilder();
printUnknownFields(fields, singleLineOutput(text));
printUnknownFields(fields, setSingleLineOutput(text, true));
return text.toString();
} catch (IOException e) {
throw new IllegalStateException(e);
Expand Down Expand Up @@ -640,12 +752,8 @@ public static String unsignedToString(final long value) {
}
}

private static TextGenerator multiLineOutput(Appendable output) {
return new TextGenerator(output, false);
}

private static TextGenerator singleLineOutput(Appendable output) {
return new TextGenerator(output, true);
private static TextGenerator setSingleLineOutput(Appendable output, boolean singleLine) {
return new TextGenerator(output, singleLine);
}

/** An inner class for writing text to the output stream. */
Expand Down
Loading

0 comments on commit bb68eb2

Please sign in to comment.