-
-
Notifications
You must be signed in to change notification settings - Fork 432
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add runtime classes for FileIOStream instrumentation (#1826)
- Loading branch information
Showing
11 changed files
with
859 additions
and
0 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
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
112 changes: 112 additions & 0 deletions
112
sentry/src/main/java/io/sentry/instrumentation/file/FileIOSpanManager.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,112 @@ | ||
package io.sentry.instrumentation.file; | ||
|
||
import io.sentry.IHub; | ||
import io.sentry.ISpan; | ||
import io.sentry.SpanStatus; | ||
import io.sentry.util.Platform; | ||
import io.sentry.util.StringUtils; | ||
import java.io.Closeable; | ||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.FileOutputStream; | ||
import java.io.IOException; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
final class FileIOSpanManager { | ||
|
||
private final @Nullable ISpan currentSpan; | ||
private final @Nullable File file; | ||
private final boolean isSendDefaultPii; | ||
|
||
private @NotNull SpanStatus spanStatus = SpanStatus.OK; | ||
private long byteCount; | ||
|
||
static @Nullable ISpan startSpan(final @NotNull IHub hub, final @NotNull String op) { | ||
final ISpan parent = hub.getSpan(); | ||
return parent != null ? parent.startChild(op) : null; | ||
} | ||
|
||
FileIOSpanManager( | ||
final @Nullable ISpan currentSpan, | ||
final @Nullable File file, | ||
final boolean isSendDefaultPii) { | ||
this.currentSpan = currentSpan; | ||
this.file = file; | ||
this.isSendDefaultPii = isSendDefaultPii; | ||
} | ||
|
||
/** | ||
* Performs file IO, counts the read/written bytes and handles exceptions in case of occurence | ||
* | ||
* @param operation An IO operation to execute (e.g. {@link FileInputStream#read()} or {@link | ||
* FileOutputStream#write(int)} The operation is of a type {@link Integer} or {@link Long}, | ||
* where the output is the result of the IO operation (byte count read/written) | ||
*/ | ||
<T> T performIO(final @NotNull FileIOCallable<T> operation) throws IOException { | ||
try { | ||
final T result = operation.call(); | ||
if (result instanceof Integer) { | ||
final int resUnboxed = (int) result; | ||
if (resUnboxed != -1) { | ||
byteCount += resUnboxed; | ||
} | ||
} else if (result instanceof Long) { | ||
final long resUnboxed = (long) result; | ||
if (resUnboxed != -1L) { | ||
byteCount += resUnboxed; | ||
} | ||
} | ||
return result; | ||
} catch (IOException exception) { | ||
spanStatus = SpanStatus.INTERNAL_ERROR; | ||
if (currentSpan != null) { | ||
currentSpan.setThrowable(exception); | ||
} | ||
throw exception; | ||
} | ||
} | ||
|
||
void finish(final @NotNull Closeable delegate) throws IOException { | ||
try { | ||
delegate.close(); | ||
} catch (IOException exception) { | ||
spanStatus = SpanStatus.INTERNAL_ERROR; | ||
if (currentSpan != null) { | ||
currentSpan.setThrowable(exception); | ||
} | ||
throw exception; | ||
} finally { | ||
finishSpan(); | ||
} | ||
} | ||
|
||
private void finishSpan() { | ||
if (currentSpan != null) { | ||
final String byteCountToString = StringUtils.byteCountToString(byteCount); | ||
if (file != null) { | ||
final String description = file.getName() + " " + "(" + byteCountToString + ")"; | ||
currentSpan.setDescription(description); | ||
if (Platform.isAndroid() || isSendDefaultPii) { | ||
currentSpan.setData("file.path", file.getAbsolutePath()); | ||
} | ||
} else { | ||
currentSpan.setDescription(byteCountToString); | ||
} | ||
currentSpan.setData("file.size", byteCount); | ||
currentSpan.finish(spanStatus); | ||
} | ||
} | ||
|
||
/** | ||
* A task that returns a result and may throw an IOException. Implementors define a single method | ||
* with no arguments called {@code call}. | ||
* | ||
* <p>Derived from {@link java.util.concurrent.Callable} | ||
*/ | ||
@FunctionalInterface | ||
interface FileIOCallable<T> { | ||
|
||
T call() throws IOException; | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
sentry/src/main/java/io/sentry/instrumentation/file/FileInputStreamInitData.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,26 @@ | ||
package io.sentry.instrumentation.file; | ||
|
||
import io.sentry.ISpan; | ||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
final class FileInputStreamInitData { | ||
|
||
final @Nullable File file; | ||
final @Nullable ISpan span; | ||
final @NotNull FileInputStream delegate; | ||
final boolean isSendDefaultPii; | ||
|
||
FileInputStreamInitData( | ||
final @Nullable File file, | ||
final @Nullable ISpan span, | ||
final @NotNull FileInputStream delegate, | ||
final boolean isSendDefaultPii) { | ||
this.file = file; | ||
this.span = span; | ||
this.delegate = delegate; | ||
this.isSendDefaultPii = isSendDefaultPii; | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
sentry/src/main/java/io/sentry/instrumentation/file/FileOutputStreamInitData.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,29 @@ | ||
package io.sentry.instrumentation.file; | ||
|
||
import io.sentry.ISpan; | ||
import java.io.File; | ||
import java.io.FileOutputStream; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
final class FileOutputStreamInitData { | ||
|
||
final @Nullable File file; | ||
final @Nullable ISpan span; | ||
final boolean append; | ||
final @NotNull FileOutputStream delegate; | ||
final boolean isSendDefaultPii; | ||
|
||
FileOutputStreamInitData( | ||
final @Nullable File file, | ||
final boolean append, | ||
final @Nullable ISpan span, | ||
final @NotNull FileOutputStream delegate, | ||
final boolean isSendDefaultPii) { | ||
this.file = file; | ||
this.append = append; | ||
this.span = span; | ||
this.delegate = delegate; | ||
this.isSendDefaultPii = isSendDefaultPii; | ||
} | ||
} |
143 changes: 143 additions & 0 deletions
143
sentry/src/main/java/io/sentry/instrumentation/file/SentryFileInputStream.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,143 @@ | ||
package io.sentry.instrumentation.file; | ||
|
||
import io.sentry.HubAdapter; | ||
import io.sentry.IHub; | ||
import io.sentry.ISpan; | ||
import java.io.File; | ||
import java.io.FileDescriptor; | ||
import java.io.FileInputStream; | ||
import java.io.FileNotFoundException; | ||
import java.io.IOException; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
/** | ||
* An implementation of {@link java.io.FileInputStream} that creates a {@link io.sentry.ISpan} for | ||
* reading operation with filename and byte count set as description | ||
* | ||
* <p>Note, that span is started when this InputStream is instantiated via constructor and finishes | ||
* when the {@link java.io.FileInputStream#close()} is called. | ||
*/ | ||
public final class SentryFileInputStream extends FileInputStream { | ||
|
||
private final @NotNull FileInputStream delegate; | ||
private final @NotNull FileIOSpanManager spanManager; | ||
|
||
public SentryFileInputStream(final @Nullable String name) throws FileNotFoundException { | ||
this(name != null ? new File(name) : null, HubAdapter.getInstance()); | ||
} | ||
|
||
public SentryFileInputStream(final @Nullable File file) throws FileNotFoundException { | ||
this(file, HubAdapter.getInstance()); | ||
} | ||
|
||
public SentryFileInputStream(final @NotNull FileDescriptor fdObj) { | ||
this(fdObj, HubAdapter.getInstance()); | ||
} | ||
|
||
SentryFileInputStream(final @Nullable File file, final @NotNull IHub hub) | ||
throws FileNotFoundException { | ||
this(init(file, null, hub)); | ||
} | ||
|
||
SentryFileInputStream(final @NotNull FileDescriptor fdObj, final @NotNull IHub hub) { | ||
this(init(fdObj, null, hub), fdObj); | ||
} | ||
|
||
private SentryFileInputStream( | ||
final @NotNull FileInputStreamInitData data, final @NotNull FileDescriptor fd) { | ||
super(fd); | ||
spanManager = new FileIOSpanManager(data.span, data.file, data.isSendDefaultPii); | ||
delegate = data.delegate; | ||
} | ||
|
||
private SentryFileInputStream(final @NotNull FileInputStreamInitData data) | ||
throws FileNotFoundException { | ||
super(data.file); | ||
spanManager = new FileIOSpanManager(data.span, data.file, data.isSendDefaultPii); | ||
delegate = data.delegate; | ||
} | ||
|
||
private static FileInputStreamInitData init( | ||
final @Nullable File file, @Nullable FileInputStream delegate, final @NotNull IHub hub) | ||
throws FileNotFoundException { | ||
final ISpan span = FileIOSpanManager.startSpan(hub, "file.read"); | ||
if (delegate == null) { | ||
delegate = new FileInputStream(file); | ||
} | ||
return new FileInputStreamInitData(file, span, delegate, hub.getOptions().isSendDefaultPii()); | ||
} | ||
|
||
private static FileInputStreamInitData init( | ||
final @NotNull FileDescriptor fd, | ||
@Nullable FileInputStream delegate, | ||
final @NotNull IHub hub) { | ||
final ISpan span = FileIOSpanManager.startSpan(hub, "file.read"); | ||
if (delegate == null) { | ||
delegate = new FileInputStream(fd); | ||
} | ||
return new FileInputStreamInitData(null, span, delegate, hub.getOptions().isSendDefaultPii()); | ||
} | ||
|
||
@Override | ||
public int read() throws IOException { | ||
// this is the only case, when the read() operation returns the byte value, and not the count | ||
// hence we need this special handling | ||
AtomicInteger result = new AtomicInteger(0); | ||
spanManager.performIO( | ||
() -> { | ||
final int res = delegate.read(); | ||
result.set(res); | ||
return res != -1 ? 1 : 0; | ||
}); | ||
return result.get(); | ||
} | ||
|
||
@Override | ||
public int read(final byte @NotNull [] b) throws IOException { | ||
return spanManager.performIO(() -> delegate.read(b)); | ||
} | ||
|
||
@Override | ||
public int read(final byte @NotNull [] b, final int off, final int len) throws IOException { | ||
return spanManager.performIO(() -> delegate.read(b, off, len)); | ||
} | ||
|
||
@Override | ||
public long skip(final long n) throws IOException { | ||
return spanManager.performIO(() -> delegate.skip(n)); | ||
} | ||
|
||
@Override | ||
public void close() throws IOException { | ||
spanManager.finish(delegate); | ||
} | ||
|
||
public static final class Factory { | ||
public static FileInputStream create( | ||
final @NotNull FileInputStream delegate, final @Nullable String name) | ||
throws FileNotFoundException { | ||
return new SentryFileInputStream( | ||
init(name != null ? new File(name) : null, delegate, HubAdapter.getInstance())); | ||
} | ||
|
||
public static FileInputStream create( | ||
final @NotNull FileInputStream delegate, final @Nullable File file) | ||
throws FileNotFoundException { | ||
return new SentryFileInputStream(init(file, delegate, HubAdapter.getInstance())); | ||
} | ||
|
||
public static FileInputStream create( | ||
final @NotNull FileInputStream delegate, final @NotNull FileDescriptor descriptor) { | ||
return new SentryFileInputStream( | ||
init(descriptor, delegate, HubAdapter.getInstance()), descriptor); | ||
} | ||
|
||
static FileInputStream create( | ||
final @NotNull FileInputStream delegate, final @Nullable File file, final @NotNull IHub hub) | ||
throws FileNotFoundException { | ||
return new SentryFileInputStream(init(file, delegate, hub)); | ||
} | ||
} | ||
} |
Oops, something went wrong.