diff --git a/sentry/src/main/java/io/sentry/instrumentation/file/FileIOSpanManager.java b/sentry/src/main/java/io/sentry/instrumentation/file/FileIOSpanManager.java index 3583530cd9..aa690f1420 100644 --- a/sentry/src/main/java/io/sentry/instrumentation/file/FileIOSpanManager.java +++ b/sentry/src/main/java/io/sentry/instrumentation/file/FileIOSpanManager.java @@ -93,9 +93,9 @@ private void finishSpan() { if (currentSpan != null) { final String byteCountToString = StringUtils.byteCountToString(byteCount); if (file != null) { - final String description = file.getName() + " " + "(" + byteCountToString + ")"; + final String description = getDescription(file); currentSpan.setDescription(description); - if (Platform.isAndroid() || options.isSendDefaultPii()) { + if (options.isSendDefaultPii()) { currentSpan.setData("file.path", file.getAbsolutePath()); } } else { @@ -112,6 +112,22 @@ private void finishSpan() { } } + private @NotNull String getDescription(final @NotNull File file) { + final String byteCountToString = StringUtils.byteCountToString(byteCount); + // if we send PII, we can send the file name directly + if (options.isSendDefaultPii()) { + return file.getName() + " (" + byteCountToString + ")"; + } + final int lastDotIndex = file.getName().lastIndexOf('.'); + // if the file has an extension, show it in the description, even without sending PII + if (lastDotIndex > 0 && lastDotIndex < file.getName().length() - 1) { + final String fileExtension = file.getName().substring(lastDotIndex); + return "***" + fileExtension + " (" + byteCountToString + ")"; + } else { + return "***" + " (" + byteCountToString + ")"; + } + } + /** * A task that returns a result and may throw an IOException. Implementors define a single method * with no arguments called {@code call}. diff --git a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileInputStreamTest.kt b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileInputStreamTest.kt index 08f56a0666..def4e8c555 100644 --- a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileInputStreamTest.kt +++ b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileInputStreamTest.kt @@ -23,6 +23,7 @@ import kotlin.concurrent.thread import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -80,6 +81,8 @@ class SentryFileInputStreamTest { private val tmpFile: File get() = tmpDir.newFile("test.txt") + private val tmpFileWithoutExtension: File get() = tmpDir.newFile("test") + @Test fun `when no active transaction does not capture a span`() { fixture.getSut(tmpFile, activeTransaction = false) @@ -104,13 +107,42 @@ class SentryFileInputStreamTest { assertEquals(fixture.sentryTracer.children.size, 1) val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (0 B)") assertEquals(fileIOSpan.data["file.size"], 0L) assertEquals(fileIOSpan.throwable, null) assertEquals(fileIOSpan.isFinished, true) assertEquals(fileIOSpan.status, SpanStatus.OK) } + @Test + fun `captures file name in description and file path when isSendDefaultPii is true`() { + val fis = fixture.getSut(tmpFile, sendDefaultPii = true) + fis.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "test.txt (0 B)") + assertNotNull(fileIOSpan.data["file.path"]) + } + + @Test + fun `captures only file extension in description when isSendDefaultPii is false`() { + val fis = fixture.getSut(tmpFile, sendDefaultPii = false) + fis.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "***.txt (0 B)") + assertNull(fileIOSpan.data["file.path"]) + } + + @Test + fun `captures only file size if no extension is available when isSendDefaultPii is false`() { + val fis = fixture.getSut(tmpFileWithoutExtension, sendDefaultPii = false) + fis.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "*** (0 B)") + assertNull(fileIOSpan.data["file.path"]) + } + @Test fun `when stream is closed, releases file descriptor`() { val fis = fixture.getSut(tmpFile) @@ -123,7 +155,7 @@ class SentryFileInputStreamTest { fixture.getSut(tmpFile).use { it.read() } val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (1 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (1 B)") assertEquals(fileIOSpan.data["file.size"], 1L) } @@ -132,7 +164,7 @@ class SentryFileInputStreamTest { fixture.getSut(tmpFile).use { it.read(ByteArray(10)) } val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)") assertEquals(fileIOSpan.data["file.size"], 4L) } @@ -141,7 +173,7 @@ class SentryFileInputStreamTest { fixture.getSut(tmpFile).use { it.read(ByteArray(10), 1, 3) } val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (3 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (3 B)") assertEquals(fileIOSpan.data["file.size"], 3L) } @@ -150,7 +182,7 @@ class SentryFileInputStreamTest { fixture.getSut(tmpFile).use { it.skip(10) } val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (10 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (10 B)") assertEquals(fileIOSpan.data["file.size"], 10L) } @@ -159,7 +191,7 @@ class SentryFileInputStreamTest { fixture.getSut(tmpFile).use { it.reader().readText() } val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)") assertEquals(fileIOSpan.data["file.size"], 4L) } diff --git a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileOutputStreamTest.kt b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileOutputStreamTest.kt index 55f47c9b98..f1826e5544 100644 --- a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileOutputStreamTest.kt +++ b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileOutputStreamTest.kt @@ -19,6 +19,7 @@ import kotlin.concurrent.thread import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -30,14 +31,15 @@ class SentryFileOutputStreamTest { internal fun getSut( tmpFile: File? = null, activeTransaction: Boolean = true, - append: Boolean = false + append: Boolean = false, + optionsConfiguration: (SentryOptions) -> Unit = {} ): SentryFileOutputStream { - whenever(scopes.options).thenReturn( - SentryOptions().apply { - threadChecker = ThreadChecker.getInstance() - addInAppInclude("org.junit") - } - ) + val options = SentryOptions().apply { + threadChecker = ThreadChecker.getInstance() + addInAppInclude("org.junit") + optionsConfiguration(this) + } + whenever(scopes.options).thenReturn(options) sentryTracer = SentryTracer(TransactionContext("name", "op"), scopes) if (activeTransaction) { whenever(scopes.span).thenReturn(sentryTracer) @@ -53,6 +55,8 @@ class SentryFileOutputStreamTest { private val tmpFile: File get() = tmpDir.newFile("test.txt") + private val tmpFileWithoutExtension: File get() = tmpDir.newFile("test") + @Test fun `when no active transaction does not capture a span`() { fixture.getSut(tmpFile, activeTransaction = false) @@ -77,13 +81,49 @@ class SentryFileOutputStreamTest { assertEquals(fixture.sentryTracer.children.size, 1) val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (0 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (0 B)") assertEquals(fileIOSpan.data["file.size"], 0L) assertEquals(fileIOSpan.throwable, null) assertEquals(fileIOSpan.isFinished, true) assertEquals(fileIOSpan.status, SpanStatus.OK) } + @Test + fun `captures file name in description and file path when isSendDefaultPii is true`() { + val fos = fixture.getSut(tmpFile) { + it.isSendDefaultPii = true + } + fos.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "test.txt (0 B)") + assertNotNull(fileIOSpan.data["file.path"]) + } + + @Test + fun `captures only file extension in description when isSendDefaultPii is false`() { + val fos = fixture.getSut(tmpFile) { + it.isSendDefaultPii = false + } + fos.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "***.txt (0 B)") + assertNull(fileIOSpan.data["file.path"]) + } + + @Test + fun `captures only file size if no extension is available when isSendDefaultPii is false`() { + val fos = fixture.getSut(tmpFileWithoutExtension) { + it.isSendDefaultPii = false + } + fos.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "*** (0 B)") + assertNull(fileIOSpan.data["file.path"]) + } + @Test fun `when stream is closed file descriptor is also closed`() { val fos = fixture.getSut(tmpFile) @@ -96,7 +136,7 @@ class SentryFileOutputStreamTest { fixture.getSut(tmpFile).use { it.write(29) } val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (1 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (1 B)") assertEquals(fileIOSpan.data["file.size"], 1L) } @@ -105,7 +145,7 @@ class SentryFileOutputStreamTest { fixture.getSut(tmpFile).use { it.write(ByteArray(10)) } val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (10 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (10 B)") assertEquals(fileIOSpan.data["file.size"], 10L) } @@ -114,7 +154,7 @@ class SentryFileOutputStreamTest { fixture.getSut(tmpFile).use { it.write(ByteArray(10), 1, 3) } val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (3 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (3 B)") assertEquals(fileIOSpan.data["file.size"], 3L) } @@ -123,7 +163,7 @@ class SentryFileOutputStreamTest { fixture.getSut(tmpFile).use { it.write("Text".toByteArray()) } val fileIOSpan = fixture.sentryTracer.children.first() - assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)") assertEquals(fileIOSpan.data["file.size"], 4L) } diff --git a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileReaderTest.kt b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileReaderTest.kt index 859a71e8b9..0f8240e301 100644 --- a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileReaderTest.kt +++ b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileReaderTest.kt @@ -14,6 +14,8 @@ import org.mockito.kotlin.whenever import java.io.File import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull class SentryFileReaderTest { class Fixture { @@ -22,14 +24,15 @@ class SentryFileReaderTest { internal fun getSut( tmpFile: File, - activeTransaction: Boolean = true + activeTransaction: Boolean = true, + optionsConfiguration: (SentryOptions) -> Unit = {} ): SentryFileReader { tmpFile.writeText("TEXT") - whenever(scopes.options).thenReturn( - SentryOptions().apply { - threadChecker = ThreadChecker.getInstance() - } - ) + val options = SentryOptions().apply { + threadChecker = ThreadChecker.getInstance() + optionsConfiguration(this) + } + whenever(scopes.options).thenReturn(options) sentryTracer = SentryTracer(TransactionContext("name", "op"), scopes) if (activeTransaction) { whenever(scopes.span).thenReturn(sentryTracer) @@ -45,6 +48,8 @@ class SentryFileReaderTest { private val tmpFile: File get() = tmpDir.newFile("test.txt") + private val tmpFileWithoutExtension: File get() = tmpDir.newFile("test") + @Test fun `captures a span`() { val reader = fixture.getSut(tmpFile) @@ -54,11 +59,50 @@ class SentryFileReaderTest { assertEquals(fixture.sentryTracer.children.size, 1) val fileIOSpan = fixture.sentryTracer.children.first() assertEquals(fileIOSpan.spanContext.operation, "file.read") - assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)") assertEquals(fileIOSpan.data["file.size"], 4L) assertEquals(fileIOSpan.throwable, null) assertEquals(fileIOSpan.isFinished, true) assertEquals(fileIOSpan.data[SpanDataConvention.BLOCKED_MAIN_THREAD_KEY], true) assertEquals(fileIOSpan.status, OK) } + + @Test + fun `captures file name in description and file path when isSendDefaultPii is true`() { + val reader = fixture.getSut(tmpFile) { + it.isSendDefaultPii = true + } + reader.readText() + reader.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)") + assertNotNull(fileIOSpan.data["file.path"]) + } + + @Test + fun `captures only file extension in description when isSendDefaultPii is false`() { + val reader = fixture.getSut(tmpFile) { + it.isSendDefaultPii = false + } + reader.readText() + reader.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)") + assertNull(fileIOSpan.data["file.path"]) + } + + @Test + fun `captures only file size if no extension is available when isSendDefaultPii is false`() { + val reader = fixture.getSut(tmpFileWithoutExtension) { + it.isSendDefaultPii = false + } + reader.readText() + reader.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "*** (4 B)") + assertNull(fileIOSpan.data["file.path"]) + } } diff --git a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileWriterTest.kt b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileWriterTest.kt index a2ad8f01f7..8c8f2238d4 100644 --- a/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileWriterTest.kt +++ b/sentry/src/test/java/io/sentry/instrumentation/file/SentryFileWriterTest.kt @@ -14,6 +14,8 @@ import org.mockito.kotlin.whenever import java.io.File import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull class SentryFileWriterTest { class Fixture { @@ -23,13 +25,14 @@ class SentryFileWriterTest { internal fun getSut( tmpFile: File, activeTransaction: Boolean = true, - append: Boolean = false + append: Boolean = false, + optionsConfiguration: (SentryOptions) -> Unit = {} ): SentryFileWriter { - whenever(scopes.options).thenReturn( - SentryOptions().apply { - threadChecker = ThreadChecker.getInstance() - } - ) + val options = SentryOptions().apply { + threadChecker = ThreadChecker.getInstance() + optionsConfiguration(this) + } + whenever(scopes.options).thenReturn(options) sentryTracer = SentryTracer(TransactionContext("name", "op"), scopes) if (activeTransaction) { whenever(scopes.span).thenReturn(sentryTracer) @@ -45,6 +48,8 @@ class SentryFileWriterTest { private val tmpFile: File by lazy { tmpDir.newFile("test.txt") } + private val tmpFileWithoutExtension: File by lazy { tmpDir.newFile("test") } + @Test fun `captures a span`() { val writer = fixture.getSut(tmpFile) @@ -54,7 +59,7 @@ class SentryFileWriterTest { assertEquals(fixture.sentryTracer.children.size, 1) val fileIOSpan = fixture.sentryTracer.children.first() assertEquals(fileIOSpan.spanContext.operation, "file.write") - assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)") + assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)") assertEquals(fileIOSpan.data["file.size"], 4L) assertEquals(fileIOSpan.throwable, null) assertEquals(fileIOSpan.isFinished, true) @@ -77,4 +82,43 @@ class SentryFileWriterTest { assertEquals("testtest2", tmpFile.readText()) } + + @Test + fun `captures file name in description and file path when isSendDefaultPii is true`() { + val writer = fixture.getSut(tmpFile) { + it.isSendDefaultPii = true + } + writer.write("TEXT") + writer.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "test.txt (4 B)") + assertNotNull(fileIOSpan.data["file.path"]) + } + + @Test + fun `captures only file extension in description when isSendDefaultPii is false`() { + val writer = fixture.getSut(tmpFile) { + it.isSendDefaultPii = false + } + writer.write("TEXT") + writer.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "***.txt (4 B)") + assertNull(fileIOSpan.data["file.path"]) + } + + @Test + fun `captures only file size if no extension is available when isSendDefaultPii is false`() { + val writer = fixture.getSut(tmpFileWithoutExtension) { + it.isSendDefaultPii = false + } + writer.write("TEXT") + writer.close() + + val fileIOSpan = fixture.sentryTracer.children.first() + assertEquals(fileIOSpan.spanContext.description, "*** (4 B)") + assertNull(fileIOSpan.data["file.path"]) + } }