From 4e47438b8e9d78cbae7980917ecaa1a7fdf0af66 Mon Sep 17 00:00:00 2001
From: Matthias Urhahn <matthias.urhahn@sap.com>
Date: Fri, 21 May 2021 12:40:18 +0200
Subject: [PATCH] Fix debug log censoring collisions (EXPOSUREAPP-7196) (#3230)

* Detect censoring collision and replace whole line with placeholder.
Give the debug logger an additional thread to parallelize censoring.

* Smart censoring collision handling.

* Tweak logging performance.

* Adjusted unit tests to censoring changes.

* Print stacktraces to debug log too!

* Make KLINT happy

* Adjust tests to changed log formatting.

* Add tests for throwable formatting in debuglogger lines.

* Re-enable debug tree.

* Fix incorrect censoring range. We need the range of the original match, not it's replacement.

* Fix duplicate new line if exceptions are logged.

Co-authored-by: Kolya Opahle <k.opahle@sap.com>
Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com>
Co-authored-by: Mohamed Metwalli <mohamed.metwalli@sap.com>
---
 .../bugreporting/censors/BugCensor.kt         | 40 ++++++++-
 .../contactdiary/DiaryEncounterCensor.kt      | 14 ++--
 .../contactdiary/DiaryLocationCensor.kt       | 18 +++--
 .../censors/contactdiary/DiaryPersonCensor.kt | 18 +++--
 .../censors/contactdiary/DiaryVisitCensor.kt  | 14 ++--
 .../censors/presencetracing/CheckInsCensor.kt | 16 ++--
 .../presencetracing/TraceLocationCensor.kt    | 24 +++---
 .../censors/submission/CoronaTestCensor.kt    | 48 +++++++----
 .../censors/submission/PcrQrCodeCensor.kt     | 13 ++-
 .../censors/submission/RACoronaTestCensor.kt  | 18 +++--
 .../censors/submission/RatProfileCensor.kt    | 31 ++++---
 .../censors/submission/RatQrCodeCensor.kt     | 22 ++---
 .../vaccination/CertificateQrCodeCensor.kt    | 57 +++++++------
 .../bugreporting/debuglog/DebugLogger.kt      | 28 ++++++-
 .../bugreporting/debuglog/LogLine.kt          | 36 +++++++--
 .../debuglog/internal/DebugLogStorageCheck.kt | 10 +--
 .../debuglog/internal/DebugLoggerScope.kt     |  3 +-
 .../debuglog/internal/LogWriter.kt            | 13 +--
 .../bugreporting/censors/BugCensorTest.kt     | 16 +---
 .../censors/CheckInsCensorTest.kt             | 55 ++++---------
 .../censors/CoronaTestCensorTest.kt           | 48 +++--------
 .../censors/DiaryEncounterCensorTest.kt       | 81 ++++++-------------
 .../censors/DiaryLocationCensorTest.kt        | 71 +++++-----------
 .../censors/DiaryPersonCensorTest.kt          | 71 +++++-----------
 .../censors/DiaryVisitCensorTest.kt           | 79 +++++-------------
 .../censors/PcrQrCodeCensorTest.kt            | 29 +------
 .../censors/RACoronaTestCensorTest.kt         | 51 ++++--------
 .../censors/RatQrCodeCensorTest.kt            | 30 ++-----
 .../censors/TraceLocationCensorTest.kt        | 77 ++++++------------
 .../submission/RatProfileCensorTest.kt        | 42 +++-------
 .../CertificateQrCodeCensorTest.kt            | 55 +++----------
 .../bugreporting/debuglog/DebugLoggerTest.kt  | 63 +++++++++++----
 .../bugreporting/debuglog/LogLineTest.kt      | 39 +++++++++
 .../internal/DebugLogStorageCheckTest.kt      | 40 +++++----
 .../debuglog/internal/LogSnapshotterTest.kt   |  9 ++-
 35 files changed, 571 insertions(+), 708 deletions(-)
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLineTest.kt

diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt
index 48c13a4a7..57cf1c85a 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt
@@ -1,15 +1,47 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
+import kotlin.math.max
+import kotlin.math.min
 
 interface BugCensor {
 
     /**
      * If there is something to censor a new log line is returned, otherwise returns null
      */
-    suspend fun checkLog(entry: LogLine): LogLine?
+    suspend fun checkLog(message: String): CensoredString?
+
+    data class CensoredString(
+        // The censored version of the string
+        val string: String,
+        // The range that we censored
+        // If there is a collision, this range in the original needs to be removed.
+        val range: IntRange? = null
+    )
 
     companion object {
+        operator fun CensoredString.plus(newer: CensoredString?): CensoredString {
+            if (newer == null) return this
+
+            val range = when {
+                newer.range == null -> this.range
+                this.range == null -> newer.range
+                else -> min(this.range.first, newer.range.first)..max(this.range.last, newer.range.last)
+            }
+
+            return CensoredString(string = newer.string, range = range)
+        }
+
+        fun CensoredString.censor(orig: String, replacement: String): CensoredString? {
+            val start = this.string.indexOf(orig)
+            if (start == -1) return null
+
+            val end = start + orig.length
+            return CensoredString(
+                string = this.string.replace(orig, replacement),
+                range = start..end
+            )
+        }
+
         fun withValidName(name: String?, action: (String) -> Unit): Boolean {
             if (name.isNullOrBlank()) return false
             if (name.length < 3) return false
@@ -66,8 +98,8 @@ interface BugCensor {
             return true
         }
 
-        fun LogLine.toNewLogLineIfDifferent(newMessage: String): LogLine? {
-            return if (newMessage != message) copy(message = newMessage) else null
+        fun CensoredString.toNullIfUnmodified(): CensoredString? {
+            return if (range == null) null else this
         }
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryEncounterCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryEncounterCensor.kt
index 9c75a723f..0814fd1f7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryEncounterCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryEncounterCensor.kt
@@ -2,9 +2,11 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidComment
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
 import kotlinx.coroutines.CoroutineScope
@@ -28,21 +30,21 @@ class DiaryEncounterCensor @Inject constructor(
         ).filterNotNull()
     }
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
+    override suspend fun checkLog(message: String): CensoredString? {
         val encountersNow = encounters.first().filter { !it.circumstances.isNullOrBlank() }
 
         if (encountersNow.isEmpty()) return null
 
-        val newMessage = encountersNow.fold(entry.message) { orig, encounter ->
+        val newMessage = encountersNow.fold(CensoredString(message)) { orig, encounter ->
             var wip = orig
 
             withValidComment(encounter.circumstances) {
-                wip = wip.replace(it, "Encounter#${encounter.id}/Circumstances")
+                wip += wip.censor(it, "Encounter#${encounter.id}/Circumstances")
             }
 
             wip
         }
 
-        return entry.toNewLogLineIfDifferent(newMessage)
+        return newMessage.toNullIfUnmodified()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryLocationCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryLocationCensor.kt
index 70628444c..1ee602135 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryLocationCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryLocationCensor.kt
@@ -2,11 +2,13 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidEmail
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidPhoneNumber
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
 import kotlinx.coroutines.CoroutineScope
@@ -30,27 +32,27 @@ class DiaryLocationCensor @Inject constructor(
         ).filterNotNull()
     }
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
+    override suspend fun checkLog(message: String): CensoredString? {
         val locationsNow = locations.first()
 
         if (locationsNow.isEmpty()) return null
 
-        val newMessage = locationsNow.fold(entry.message) { orig, location ->
+        val newMessage = locationsNow.fold(CensoredString(message)) { orig, location ->
             var wip = orig
 
             withValidName(location.locationName) {
-                wip = wip.replace(it, "Location#${location.locationId}/Name")
+                wip += wip.censor(it, "Location#${location.locationId}/Name")
             }
             withValidEmail(location.emailAddress) {
-                wip = wip.replace(it, "Location#${location.locationId}/EMail")
+                wip += wip.censor(it, "Location#${location.locationId}/EMail")
             }
             withValidPhoneNumber(location.phoneNumber) {
-                wip = wip.replace(it, "Location#${location.locationId}/PhoneNumber")
+                wip += wip.censor(it, "Location#${location.locationId}/PhoneNumber")
             }
 
             wip
         }
 
-        return entry.toNewLogLineIfDifferent(newMessage)
+        return newMessage.toNullIfUnmodified()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryPersonCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryPersonCensor.kt
index a8a0056d9..d525cde21 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryPersonCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryPersonCensor.kt
@@ -2,11 +2,13 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidEmail
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidPhoneNumber
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
 import kotlinx.coroutines.CoroutineScope
@@ -30,27 +32,27 @@ class DiaryPersonCensor @Inject constructor(
         ).filterNotNull()
     }
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
+    override suspend fun checkLog(message: String): CensoredString? {
         val personsNow = persons.first()
 
         if (personsNow.isEmpty()) return null
 
-        val newMessage = personsNow.fold(entry.message) { orig, person ->
+        val newMessage = personsNow.fold(CensoredString(message)) { orig, person ->
             var wip = orig
 
             withValidName(person.fullName) {
-                wip = wip.replace(it, "Person#${person.personId}/Name")
+                wip += wip.censor(it, "Person#${person.personId}/Name")
             }
             withValidEmail(person.emailAddress) {
-                wip = wip.replace(it, "Person#${person.personId}/EMail")
+                wip += wip.censor(it, "Person#${person.personId}/EMail")
             }
             withValidPhoneNumber(person.phoneNumber) {
-                wip = wip.replace(it, "Person#${person.personId}/PhoneNumber")
+                wip += wip.censor(it, "Person#${person.personId}/PhoneNumber")
             }
 
             wip
         }
 
-        return entry.toNewLogLineIfDifferent(newMessage)
+        return newMessage.toNullIfUnmodified()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryVisitCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryVisitCensor.kt
index f060c0fd2..61398937d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryVisitCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryVisitCensor.kt
@@ -2,8 +2,10 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
 import kotlinx.coroutines.CoroutineScope
@@ -27,21 +29,21 @@ class DiaryVisitCensor @Inject constructor(
         ).filterNotNull()
     }
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
+    override suspend fun checkLog(message: String): CensoredString? {
         val visitsNow = visits.first().filter { !it.circumstances.isNullOrBlank() }
 
         if (visitsNow.isEmpty()) return null
 
-        val newMessage = visitsNow.fold(entry.message) { orig, visit ->
+        val newMessage = visitsNow.fold(CensoredString(message)) { orig, visit ->
             var wip = orig
 
             BugCensor.withValidComment(visit.circumstances) {
-                wip = wip.replace(it, "Visit#${visit.id}/Circumstances")
+                wip += wip.censor(it, "Visit#${visit.id}/Circumstances")
             }
 
             wip
         }
 
-        return entry.toNewLogLineIfDifferent(newMessage)
+        return newMessage.toNullIfUnmodified()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/CheckInsCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/CheckInsCensor.kt
index a5f10d81c..018f5208b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/CheckInsCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/CheckInsCensor.kt
@@ -2,10 +2,12 @@ package de.rki.coronawarnapp.bugreporting.censors.presencetracing
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidAddress
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidDescription
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
 import kotlinx.coroutines.CoroutineScope
@@ -29,27 +31,27 @@ class CheckInsCensor @Inject constructor(
         ).filterNotNull()
     }
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
+    override suspend fun checkLog(message: String): CensoredString? {
 
         val checkIns = checkInsFlow.first()
 
         if (checkIns.isEmpty()) return null
 
-        val newLogMsg = checkIns.fold(entry.message) { initial, checkIn ->
+        val newLogMsg = checkIns.fold(CensoredString(message)) { initial, checkIn ->
 
             var acc = initial
 
             withValidDescription(checkIn.description) { description ->
-                acc = acc.replace(description, "CheckIn#${checkIn.id}/Description")
+                acc += acc.censor(description, "CheckIn#${checkIn.id}/Description")
             }
 
             withValidAddress(checkIn.address) { address ->
-                acc = acc.replace(address, "CheckIn#${checkIn.id}/Address")
+                acc += acc.censor(address, "CheckIn#${checkIn.id}/Address")
             }
 
             acc
         }
 
-        return entry.toNewLogLineIfDifferent(newLogMsg)
+        return newLogMsg.toNullIfUnmodified()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/TraceLocationCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/TraceLocationCensor.kt
index 6d3f60ffb..5f702727e 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/TraceLocationCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/TraceLocationCensor.kt
@@ -2,10 +2,12 @@ package de.rki.coronawarnapp.bugreporting.censors.presencetracing
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidAddress
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidDescription
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.presencetracing.locations.TraceLocationUserInput
 import de.rki.coronawarnapp.presencetracing.storage.repo.TraceLocationRepository
@@ -38,21 +40,21 @@ class TraceLocationCensor @Inject constructor(
         ).filterNotNull()
     }
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
+    override suspend fun checkLog(message: String): CensoredString? {
 
         val traceLocations = traceLocationsFlow.first()
 
-        var newLogMsg = traceLocations.fold(entry.message) { initial, traceLocation ->
+        var newLogMsg = traceLocations.fold(CensoredString(message)) { initial, traceLocation ->
             var acc = initial
 
-            acc = acc.replace(traceLocation.type.name, "TraceLocation#${traceLocation.id}/Type")
+            acc += acc.censor(traceLocation.type.name, "TraceLocation#${traceLocation.id}/Type")
 
             withValidDescription(traceLocation.description) { description ->
-                acc = acc.replace(description, "TraceLocation#${traceLocation.id}/Description")
+                acc += acc.censor(description, "TraceLocation#${traceLocation.id}/Description")
             }
 
             withValidAddress(traceLocation.address) { address ->
-                acc = acc.replace(address, "TraceLocation#${traceLocation.id}/Address")
+                acc += acc.censor(address, "TraceLocation#${traceLocation.id}/Address")
             }
 
             acc
@@ -60,18 +62,18 @@ class TraceLocationCensor @Inject constructor(
 
         val inputDataToCensor = dataToCensor
         if (inputDataToCensor != null) {
-            newLogMsg = newLogMsg.replace(inputDataToCensor.type.name, "TraceLocationUserInput#Type")
+            newLogMsg += newLogMsg.censor(inputDataToCensor.type.name, "TraceLocationUserInput#Type")
 
             withValidDescription(inputDataToCensor.description) {
-                newLogMsg = newLogMsg.replace(inputDataToCensor.description, "TraceLocationUserInput#Description")
+                newLogMsg += newLogMsg.censor(inputDataToCensor.description, "TraceLocationUserInput#Description")
             }
 
             withValidAddress(inputDataToCensor.address) {
-                newLogMsg = newLogMsg.replace(inputDataToCensor.address, "TraceLocationUserInput#Address")
+                newLogMsg += newLogMsg.censor(inputDataToCensor.address, "TraceLocationUserInput#Address")
             }
         }
 
-        return entry.toNewLogLineIfDifferent(newLogMsg)
+        return newLogMsg.toNullIfUnmodified()
     }
 
     companion object {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/CoronaTestCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/CoronaTestCensor.kt
index 8958fe290..ccac2fe3d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/CoronaTestCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/CoronaTestCensor.kt
@@ -2,44 +2,64 @@ package de.rki.coronawarnapp.bugreporting.censors.submission
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
+import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 import javax.inject.Inject
 
 @Reusable
 class CoronaTestCensor @Inject constructor(
+    @DebuggerScope debugScope: CoroutineScope,
     private val coronaTestRepository: CoronaTestRepository,
 ) : BugCensor {
 
+    private val mutex = Mutex()
+
     // Keep a history to have references even after the user deletes a test
     private val tokenHistory = mutableSetOf<String>()
     private val identifierHistory = mutableSetOf<String>()
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
+    private val coronaTestFlow by lazy {
+        coronaTestRepository.coronaTests.stateIn(
+            scope = debugScope,
+            started = SharingStarted.Lazily,
+            initialValue = null
+        ).filterNotNull().onEach { tests ->
+            // The Registration Token is received after registration of PCR and RAT tests. It is required to poll the test result.
+            tokenHistory.addAll(tests.map { it.registrationToken })
+            identifierHistory.addAll(tests.map { it.identifier })
+        }
+    }
 
-        // The Registration Token is received after registration of PCR and RAT tests. It is required to poll the test result.
-        val tokens = coronaTestRepository.coronaTests.first().map { it.registrationToken }
-        tokenHistory.addAll(tokens)
+    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
+        coronaTestFlow.first()
 
-        val identifiers = coronaTestRepository.coronaTests.first().map { it.identifier }
-        identifierHistory.addAll(identifiers)
+        var newMessage = CensoredString(message)
 
-        var newMessage = entry.message
         for (token in tokenHistory) {
-            if (!entry.message.contains(token)) continue
+            if (!message.contains(token)) continue
 
-            newMessage = newMessage.replace(token, PLACEHOLDER + token.takeLast(4))
+            newMessage += newMessage.censor(token, PLACEHOLDER + token.takeLast(4))
         }
 
         identifierHistory
-            .filter { entry.message.contains(it) }
+            .filter { message.contains(it) }
             .forEach {
-                newMessage = newMessage.replace(it, "${it.take(11)}CoronaTest/Identifier")
+                newMessage += newMessage.censor(it, "${it.take(11)}CoronaTest/Identifier")
             }
 
-        return entry.toNewLogLineIfDifferent(newMessage)
+        return newMessage.toNullIfUnmodified()
     }
 
     companion object {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/PcrQrCodeCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/PcrQrCodeCensor.kt
index 881a5396f..0c61dd0ca 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/PcrQrCodeCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/PcrQrCodeCensor.kt
@@ -2,21 +2,18 @@ package de.rki.coronawarnapp.bugreporting.censors.submission
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
 import javax.inject.Inject
 
 @Reusable
 class PcrQrCodeCensor @Inject constructor() : BugCensor {
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
-
+    override suspend fun checkLog(message: String): CensoredString? {
         val guid = lastGUID ?: return null
-        if (!entry.message.contains(guid)) return null
-
-        val newMessage = entry.message.replace(guid, PLACEHOLDER + guid.takeLast(4))
+        if (!message.contains(guid)) return null
 
-        return entry.toNewLogLineIfDifferent(newMessage)
+        return CensoredString(message).censor(guid, PLACEHOLDER + guid.takeLast(4))
     }
 
     companion object {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RACoronaTestCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RACoronaTestCensor.kt
index 38c06dca4..fff347294 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RACoronaTestCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RACoronaTestCensor.kt
@@ -1,9 +1,11 @@
 package de.rki.coronawarnapp.bugreporting.censors.submission
 
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest
@@ -31,27 +33,27 @@ class RACoronaTestCensor @Inject constructor(
         ).filterNotNull()
     }
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
+    override suspend fun checkLog(message: String): CensoredString? {
 
         val raCoronaTestFlow = coronaTestFlow.map { tests -> tests.filterIsInstance<RACoronaTest>() }.first()
         val raCoronaTest = raCoronaTestFlow.firstOrNull() ?: return null
 
-        var newMessage = entry.message
+        var newMessage = CensoredString(message)
 
         with(raCoronaTest) {
             withValidName(firstName) { firstName ->
-                newMessage = newMessage.replace(firstName, "RATest/FirstName")
+                newMessage += newMessage.censor(firstName, "RATest/FirstName")
             }
 
             withValidName(lastName) { lastName ->
-                newMessage = newMessage.replace(lastName, "RATest/LastName")
+                newMessage += newMessage.censor(lastName, "RATest/LastName")
             }
 
             val dateOfBirthString = dateOfBirth?.toString(dayOfBirthFormatter) ?: return@with
 
-            newMessage = newMessage.replace(dateOfBirthString, "RATest/DateOfBirth")
+            newMessage += newMessage.censor(dateOfBirthString, "RATest/DateOfBirth")
         }
 
-        return entry.toNewLogLineIfDifferent(newMessage)
+        return newMessage.toNullIfUnmodified()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensor.kt
index fa1e919bb..b33838de5 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensor.kt
@@ -1,16 +1,20 @@
 package de.rki.coronawarnapp.bugreporting.censors.submission
 
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidAddress
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidCity
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidPhoneNumber
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidZipCode
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.coronatest.antigen.profile.RATProfile
 import de.rki.coronawarnapp.coronatest.antigen.profile.RATProfileSettings
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 import org.joda.time.format.DateTimeFormat
 import javax.inject.Inject
 
@@ -18,10 +22,11 @@ class RatProfileCensor @Inject constructor(
     private val ratProfileSettings: RATProfileSettings
 ) : BugCensor {
 
+    private val mutex = Mutex()
     private val dayOfBirthFormatter = DateTimeFormat.forPattern("yyyy-MM-dd")
     private val ratProfileHistory = mutableSetOf<RATProfile>()
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
+    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
         val ratProfile = ratProfileSettings.profile.flow.first()
 
         // store the profile in a property so we still have a reference after it was deleted by the user
@@ -29,46 +34,46 @@ class RatProfileCensor @Inject constructor(
             ratProfileHistory.add(ratProfile)
         }
 
-        var newMessage = entry.message
+        var newMessage = CensoredString(message)
 
         ratProfileHistory.forEach { profile ->
             with(profile) {
                 withValidName(firstName) { firstName ->
-                    newMessage = newMessage.replace(firstName, "RAT-Profile/FirstName")
+                    newMessage += newMessage.censor(firstName, "RAT-Profile/FirstName")
                 }
 
                 withValidName(lastName) { lastName ->
-                    newMessage = newMessage.replace(lastName, "RAT-Profile/LastName")
+                    newMessage += newMessage.censor(lastName, "RAT-Profile/LastName")
                 }
 
                 val dateOfBirthString = birthDate?.toString(dayOfBirthFormatter)
 
                 if (dateOfBirthString != null) {
-                    newMessage = newMessage.replace(dateOfBirthString, "RAT-Profile/DateOfBirth")
+                    newMessage += newMessage.censor(dateOfBirthString, "RAT-Profile/DateOfBirth")
                 }
 
                 withValidAddress(street) { street ->
-                    newMessage = newMessage.replace(street, "RAT-Profile/Street")
+                    newMessage += newMessage.censor(street, "RAT-Profile/Street")
                 }
 
                 withValidCity(city) { city ->
-                    newMessage = newMessage.replace(city, "RAT-Profile/City")
+                    newMessage += newMessage.censor(city, "RAT-Profile/City")
                 }
 
                 withValidZipCode(zipCode) { zipCode ->
-                    newMessage = newMessage.replace(zipCode, "RAT-Profile/Zip-Code")
+                    newMessage += newMessage.censor(zipCode, "RAT-Profile/Zip-Code")
                 }
 
                 withValidPhoneNumber(phone) { phone ->
-                    newMessage = newMessage.replace(phone, "RAT-Profile/Phone")
+                    newMessage += newMessage.censor(phone, "RAT-Profile/Phone")
                 }
 
                 withValidPhoneNumber(email) { phone ->
-                    newMessage = newMessage.replace(phone, "RAT-Profile/eMail")
+                    newMessage += newMessage.censor(phone, "RAT-Profile/eMail")
                 }
             }
         }
 
-        return entry.toNewLogLineIfDifferent(newMessage)
+        return newMessage.toNullIfUnmodified()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatQrCodeCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatQrCodeCensor.kt
index 29a9c1fa5..f7537dc8d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatQrCodeCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatQrCodeCensor.kt
@@ -2,9 +2,11 @@ package de.rki.coronawarnapp.bugreporting.censors.submission
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.coronatest.qrcode.RapidAntigenHash
 import org.joda.time.LocalDate
 import org.joda.time.format.DateTimeFormat
@@ -15,31 +17,31 @@ class RatQrCodeCensor @Inject constructor() : BugCensor {
 
     private val dayOfBirthFormatter = DateTimeFormat.forPattern("yyyy-MM-dd")
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
+    override suspend fun checkLog(message: String): CensoredString? {
 
         val dataToCensor = dataToCensor ?: return null
 
-        var newMessage = entry.message
+        var newMessage = CensoredString(message)
 
         with(dataToCensor) {
-            newMessage = newMessage.replace(rawString, "RatQrCode/ScannedRawString")
+            newMessage += newMessage.censor(rawString, "RatQrCode/ScannedRawString")
 
-            newMessage = newMessage.replace(hash, PLACEHOLDER + hash.takeLast(4))
+            newMessage += newMessage.censor(hash, PLACEHOLDER + hash.takeLast(4))
 
             withValidName(firstName) { firstName ->
-                newMessage = newMessage.replace(firstName, "RATest/FirstName")
+                newMessage += newMessage.censor(firstName, "RATest/FirstName")
             }
 
             withValidName(lastName) { lastName ->
-                newMessage = newMessage.replace(lastName, "RATest/LastName")
+                newMessage += newMessage.censor(lastName, "RATest/LastName")
             }
 
             val dateOfBirthString = dateOfBirth?.toString(dayOfBirthFormatter) ?: return@with
 
-            newMessage = newMessage.replace(dateOfBirthString, "RATest/DateOfBirth")
+            newMessage += newMessage.censor(dateOfBirthString, "RATest/DateOfBirth")
         }
 
-        return entry.toNewLogLineIfDifferent(newMessage)
+        return newMessage.toNullIfUnmodified()
     }
 
     companion object {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensor.kt
index b3de1ea4f..3b1dbda24 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensor.kt
@@ -2,8 +2,10 @@ package de.rki.coronawarnapp.bugreporting.censors.vaccination
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1
 import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateData
 import java.util.LinkedList
@@ -12,86 +14,83 @@ import javax.inject.Inject
 @Reusable
 class CertificateQrCodeCensor @Inject constructor() : BugCensor {
 
-    override suspend fun checkLog(entry: LogLine): LogLine? {
-        var newMessage = entry.message
+    override suspend fun checkLog(message: String): CensoredString? {
+        var newMessage = CensoredString(message)
 
         synchronized(qrCodeStringsToCensor) { qrCodeStringsToCensor.toList() }.forEach {
-            newMessage = newMessage.replace(
-                it,
-                PLACEHOLDER + it.takeLast(4)
-            )
+            newMessage += newMessage.censor(it, PLACEHOLDER + it.takeLast(4))
         }
 
         synchronized(certsToCensor) { certsToCensor.toList() }.forEach {
             it.certificate.apply {
-                newMessage = newMessage.replace(
+                newMessage += newMessage.censor(
                     dob,
                     "vaccinationCertificate/dob"
                 )
 
-                newMessage = newMessage.replace(
+                newMessage += newMessage.censor(
                     dateOfBirth.toString(),
                     "vaccinationCertificate/dateOfBirth"
                 )
 
-                newMessage = censorNameData(nameData, newMessage)
+                newMessage += censorNameData(nameData, newMessage)
 
                 vaccinationDatas.forEach { data ->
-                    newMessage = censorVaccinationData(data, newMessage)
+                    newMessage += censorVaccinationData(data, newMessage)
                 }
             }
         }
 
-        return entry.toNewLogLineIfDifferent(newMessage)
+        return newMessage.toNullIfUnmodified()
     }
 
     private fun censorVaccinationData(
         vaccinationData: VaccinationDGCV1.VaccinationData,
-        message: String
-    ): String {
+        message: CensoredString
+    ): CensoredString {
         var newMessage = message
 
-        newMessage = newMessage.replace(
+        newMessage += newMessage.censor(
             vaccinationData.dt,
             "vaccinationData/dt"
         )
 
-        newMessage = newMessage.replace(
+        newMessage += newMessage.censor(
             vaccinationData.marketAuthorizationHolderId,
             "vaccinationData/marketAuthorizationHolderId"
         )
 
-        newMessage = newMessage.replace(
+        newMessage += newMessage.censor(
             vaccinationData.medicalProductId,
             "vaccinationData/medicalProductId"
         )
 
-        newMessage = newMessage.replace(
+        newMessage += newMessage.censor(
             vaccinationData.targetId,
             "vaccinationData/targetId"
         )
 
-        newMessage = newMessage.replace(
+        newMessage += newMessage.censor(
             vaccinationData.certificateIssuer,
             "vaccinationData/certificateIssuer"
         )
 
-        newMessage = newMessage.replace(
+        newMessage += newMessage.censor(
             vaccinationData.uniqueCertificateIdentifier,
             "vaccinationData/uniqueCertificateIdentifier"
         )
 
-        newMessage = newMessage.replace(
+        newMessage += newMessage.censor(
             vaccinationData.countryOfVaccination,
             "vaccinationData/countryOfVaccination"
         )
 
-        newMessage = newMessage.replace(
+        newMessage += newMessage.censor(
             vaccinationData.vaccineId,
             "vaccinationData/vaccineId"
         )
 
-        newMessage = newMessage.replace(
+        newMessage += newMessage.censor(
             vaccinationData.vaccinatedAt.toString(),
             "vaccinationData/vaccinatedAt"
         )
@@ -99,30 +98,30 @@ class CertificateQrCodeCensor @Inject constructor() : BugCensor {
         return newMessage
     }
 
-    private fun censorNameData(nameData: VaccinationDGCV1.NameData, message: String): String {
+    private fun censorNameData(nameData: VaccinationDGCV1.NameData, message: CensoredString): CensoredString {
         var newMessage = message
 
         nameData.familyName?.let { fName ->
-            newMessage = newMessage.replace(
+            newMessage += newMessage.censor(
                 fName,
                 "nameData/familyName"
             )
         }
 
-        newMessage = newMessage.replace(
+        newMessage += newMessage.censor(
             nameData.familyNameStandardized,
             "nameData/familyNameStandardized"
         )
 
         nameData.givenName?.let { gName ->
-            newMessage = newMessage.replace(
+            newMessage += newMessage.censor(
                 gName,
                 "nameData/givenName"
             )
         }
 
         nameData.givenNameStandardized?.let { gName ->
-            newMessage = newMessage.replace(
+            newMessage += newMessage.censor(
                 gName,
                 "nameData/givenNameStandardized"
             )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt
index 698f5b68c..5fe3b904e 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt
@@ -3,6 +3,7 @@ package de.rki.coronawarnapp.bugreporting.debuglog
 import android.annotation.SuppressLint
 import android.content.Context
 import android.util.Log
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLogStorageCheck
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLogTree
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLoggerScope
@@ -12,6 +13,8 @@ import de.rki.coronawarnapp.util.di.ApplicationComponent
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -160,10 +163,28 @@ class DebugLogger(
                 launch {
                     // Censor data sources need a moment to know what to censor
                     delay(1000)
-                    val censoredLine = bugCensors.get().fold(rawLine) { prev, censor ->
-                        censor.checkLog(prev) ?: prev
+
+                    val formattedMessage = rawLine.format()
+                    val censored: Collection<BugCensor.CensoredString> = bugCensors.get()
+                        .map {
+                            async {
+                                it.checkLog(formattedMessage)
+                            }
+                        }
+                        .awaitAll()
+                        .filterNotNull()
+                        .filter { it.range != null }
+
+                    val toWrite: String = when (censored.size) {
+                        0 -> formattedMessage
+                        1 -> censored.single().string
+                        else -> {
+                            val minMin = censored.minOf { it.range!!.first }
+                            val maxMax = censored.maxOf { it.range!!.last }
+                            formattedMessage.replaceRange(minMin, maxMax, CENSOR_COLLISION_PLACERHOLDER)
+                        }
                     }
-                    logWriter.write(censoredLine)
+                    logWriter.write(toWrite)
                 }
             }
         } catch (e: CancellationException) {
@@ -174,6 +195,7 @@ class DebugLogger(
     }
 
     companion object {
+        private const val CENSOR_COLLISION_PLACERHOLDER = "<censoring-collision>"
         internal const val TAG = "DebugLogger"
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLine.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLine.kt
index defe6c2c2..67664ffba 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLine.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLine.kt
@@ -2,6 +2,8 @@ package de.rki.coronawarnapp.bugreporting.debuglog
 
 import android.util.Log
 import org.joda.time.Instant
+import java.io.PrintWriter
+import java.io.StringWriter
 
 data class LogLine(
     val timestamp: Long,
@@ -13,15 +15,33 @@ data class LogLine(
 
     fun format(): String {
         val time = Instant.ofEpochMilli(timestamp)
-        return "$time  ${priorityLabel(priority)}/$tag: $message\n"
+
+        val baseLine = "$time ${priorityLabel(priority)}/$tag: $message"
+
+        return if (throwable != null) {
+            baseLine + "\n" + getStackTraceString(throwable)
+        } else {
+            baseLine
+        }
     }
 
-    private fun priorityLabel(priority: Int): String = when (priority) {
-        Log.ERROR -> "E"
-        Log.WARN -> "W"
-        Log.INFO -> "I"
-        Log.DEBUG -> "D"
-        Log.VERBOSE -> "V"
-        else -> priority.toString()
+    companion object {
+        // Based on Timber.Tree.getStackTraceString()
+        internal fun getStackTraceString(t: Throwable): String {
+            val sw = StringWriter(256)
+            val pw = PrintWriter(sw, false)
+            t.printStackTrace(pw)
+            pw.flush()
+            return sw.toString()
+        }
+
+        internal fun priorityLabel(priority: Int): String = when (priority) {
+            Log.ERROR -> "E"
+            Log.WARN -> "W"
+            Log.INFO -> "I"
+            Log.DEBUG -> "D"
+            Log.VERBOSE -> "V"
+            else -> priority.toString()
+        }
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLogStorageCheck.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLogStorageCheck.kt
index 113ffeb5b..1b9298774 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLogStorageCheck.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLogStorageCheck.kt
@@ -30,7 +30,7 @@ class DebugLogStorageCheck @Inject constructor(
             return eval.usableSpace
         }
 
-    fun isLowStorage(forceCheck: Boolean = false): Boolean {
+    suspend fun isLowStorage(forceCheck: Boolean = false): Boolean {
         val now = timeProvider()
         if (!forceCheck && now - lastCheckAt < 5_000) return isLowStorage.value
 
@@ -64,23 +64,23 @@ class DebugLogStorageCheck @Inject constructor(
 
     companion object {
         private const val TAG = DebugLogger.TAG
-        private val createStorageCheckErrorLine: (Throwable) -> LogLine = {
+        private val createStorageCheckErrorLine: (Throwable) -> String = {
             LogLine(
                 timestamp = System.currentTimeMillis(),
                 priority = Log.ERROR,
                 tag = TAG,
                 message = "Low storage check failed.",
                 throwable = it
-            )
+            ).format()
         }
-        private val createLowStorageLogLine: () -> LogLine = {
+        private val createLowStorageLogLine: () -> String = {
             LogLine(
                 timestamp = System.currentTimeMillis(),
                 priority = Log.WARN,
                 tag = TAG,
                 message = "Low storage, debug logger halted.",
                 throwable = null
-            )
+            ).format()
         }
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLoggerScope.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLoggerScope.kt
index 9e60dc67b..77c72ec18 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLoggerScope.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLoggerScope.kt
@@ -9,7 +9,8 @@ import javax.inject.Qualifier
 import kotlin.coroutines.CoroutineContext
 
 object DebugLoggerScope : CoroutineScope {
-    val dispatcher = Executors.newSingleThreadExecutor(
+    val dispatcher = Executors.newFixedThreadPool(
+        4,
         NamedThreadFactory("DebugLogger")
     ).asCoroutineDispatcher()
     override val coroutineContext: CoroutineContext = SupervisorJob() + dispatcher
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/LogWriter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/LogWriter.kt
index ebb188567..a5448ea4d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/LogWriter.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/LogWriter.kt
@@ -1,20 +1,22 @@
 package de.rki.coronawarnapp.bugreporting.debuglog.internal
 
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 import timber.log.Timber
 import java.io.File
 import javax.inject.Inject
 
 class LogWriter @Inject constructor(val logFile: File) {
     private var ioLimiter = 0
+    private val mutex = Mutex()
     val logSize = MutableStateFlow(logFile.length())
 
     private fun updateLogSize() {
         logSize.value = logFile.length()
     }
 
-    fun setup() {
+    suspend fun setup() = mutex.withLock {
         if (!logFile.exists()) {
             logFile.parentFile?.mkdirs()
             if (logFile.createNewFile()) {
@@ -24,16 +26,15 @@ class LogWriter @Inject constructor(val logFile: File) {
         updateLogSize()
     }
 
-    fun teardown() {
+    suspend fun teardown() = mutex.withLock {
         if (logFile.exists() && logFile.delete()) {
             Timber.d("Log file was deleted.")
         }
         updateLogSize()
     }
 
-    fun write(line: LogLine) {
-        val formattedLine = line.format()
-        logFile.appendText(formattedLine, Charsets.UTF_8)
+    suspend fun write(formattedLine: String): Unit = mutex.withLock {
+        logFile.appendText(formattedLine + "\n", Charsets.UTF_8)
 
         if (ioLimiter % 10 == 0) {
             updateLogSize()
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt
index 988042877..bb6d1f2f7 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified
 import io.kotest.matchers.shouldBe
 import io.kotest.matchers.shouldNotBe
 import org.junit.jupiter.api.Test
@@ -100,15 +99,8 @@ class BugCensorTest : BaseTest() {
     }
 
     @Test
-    fun `loglines are only copied if the message is different`() {
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Message",
-            tag = "Tag",
-            throwable = null
-        )
-        logLine.toNewLogLineIfDifferent("Message") shouldBe null
-        logLine.toNewLogLineIfDifferent("Message ") shouldNotBe logLine
+    fun `cesnsor string is nulled if not modified`() {
+        BugCensor.CensoredString("abc", 1..2).toNullIfUnmodified() shouldNotBe null
+        BugCensor.CensoredString("abc", null).toNullIfUnmodified() shouldBe null
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CheckInsCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CheckInsCensorTest.kt
index 818cdbb8e..9965c6215 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CheckInsCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CheckInsCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.censors.presencetracing.CheckInsCensor
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
 import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
 import io.kotest.matchers.shouldBe
@@ -59,36 +58,24 @@ internal class CheckInsCensorTest : BaseTest() {
 
         val censor = createInstance(this)
 
-        val logLineToCensor = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message =
-                """
-                Let's go to Moe's Tavern in Near 742 Evergreen Terrace, 12345 Springfield.
-                Who needs the Kwik-E-Mart in Some Street, 12345 Springfield? I doooo!
-                """.trimIndent(),
-            tag = "I am tag",
-            throwable = null
-        )
+        val logLineToCensor =
+            """
+            Let's go to Moe's Tavern in Near 742 Evergreen Terrace, 12345 Springfield.
+            Who needs the Kwik-E-Mart in Some Street, 12345 Springfield? I doooo!
+            """.trimIndent()
 
-        censor.checkLog(logLineToCensor) shouldBe logLineToCensor.copy(
-            message =
-                """
-                Let's go to CheckIn#1/Description in CheckIn#1/Address.
-                Who needs the CheckIn#2/Description in CheckIn#2/Address? I doooo!
-                """.trimIndent()
-        )
+        censor.checkLog(logLineToCensor)!!.string shouldBe """
+            Let's go to CheckIn#1/Description in CheckIn#1/Address.
+            Who needs the CheckIn#2/Description in CheckIn#2/Address? I doooo!
+        """.trimIndent()
 
         // censoring should still work after user deletes his check-ins
         every { checkInsRepo.allCheckIns } returns flowOf(emptyList())
 
-        censor.checkLog(logLineToCensor) shouldBe logLineToCensor.copy(
-            message =
-                """
-                Let's go to CheckIn#1/Description in CheckIn#1/Address.
-                Who needs the CheckIn#2/Description in CheckIn#2/Address? I doooo!
-                """.trimIndent()
-        )
+        censor.checkLog(logLineToCensor)!!.string shouldBe """
+            Let's go to CheckIn#1/Description in CheckIn#1/Address.
+            Who needs the CheckIn#2/Description in CheckIn#2/Address? I doooo!
+        """.trimIndent()
     }
 
     @Test
@@ -96,13 +83,7 @@ internal class CheckInsCensorTest : BaseTest() {
         every { checkInsRepo.allCheckIns } returns flowOf(emptyList())
 
         val censor = createInstance(this)
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Some log message that shouldn't be censored.",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Some log message that shouldn't be censored."
 
         censor.checkLog(logLine) shouldBe null
     }
@@ -126,13 +107,7 @@ internal class CheckInsCensorTest : BaseTest() {
         )
 
         val censor = createInstance(this)
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Some log message that shouldn't be censored.",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Some log message that shouldn't be censored."
 
         censor.checkLog(logLine) shouldBe null
     }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CoronaTestCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CoronaTestCensorTest.kt
index f80997a6a..e61a463d4 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CoronaTestCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CoronaTestCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.censors.submission.CoronaTestCensor
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
@@ -13,6 +12,7 @@ import io.mockk.impl.annotations.MockK
 import io.mockk.mockk
 import io.mockk.verify
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestCoroutineScope
 import kotlinx.coroutines.test.runBlockingTest
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
@@ -46,22 +46,15 @@ class CoronaTestCensorTest : BaseTest() {
     }
 
     private fun createInstance() = CoronaTestCensor(
+        debugScope = TestCoroutineScope(),
         coronaTestRepository = coronaTestRepository
     )
 
     @Test
     fun `censoring replaces the logline message`() = runBlockingTest {
         val instance = createInstance()
-        val filterMe = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "I'm a shy registration token: $testToken and we are extrovert $pcrIdentifier and $ratIdentifier",
-            tag = "I'm a tag",
-            throwable = null
-        )
-        instance.checkLog(filterMe) shouldBe filterMe.copy(
-            message = "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier"
-        )
+        val filterMe = "I'm a shy registration token: $testToken and we are extrovert $pcrIdentifier and $ratIdentifier"
+        instance.checkLog(filterMe)!!.string shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier"
 
         verify { coronaTestRepository.coronaTests }
     }
@@ -71,26 +64,15 @@ class CoronaTestCensorTest : BaseTest() {
         coronaTests.value = emptySet()
 
         val instance = createInstance()
-        val filterMeNot = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "I'm a shy registration token: $testToken and we are extrovert $pcrIdentifier and $ratIdentifier",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val filterMeNot =
+            "I'm a shy registration token: $testToken and we are extrovert $pcrIdentifier and $ratIdentifier"
         instance.checkLog(filterMeNot) shouldBe null
     }
 
     @Test
     fun `censoring returns null if there is no match`() = runBlockingTest {
         val instance = createInstance()
-        val filterMeNot = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "I'm not a registration token ;)",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val filterMeNot = "I'm not a registration token ;)"
         instance.checkLog(filterMeNot) shouldBe null
     }
 
@@ -99,23 +81,13 @@ class CoronaTestCensorTest : BaseTest() {
 
         val censor = createInstance()
 
-        val filterMe = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "I'm a shy registration token: $testToken and we are extrovert $pcrIdentifier and $ratIdentifier",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val filterMe = "I'm a shy registration token: $testToken and we are extrovert $pcrIdentifier and $ratIdentifier"
 
-        censor.checkLog(filterMe) shouldBe filterMe.copy(
-            message = "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier"
-        )
+        censor.checkLog(filterMe)!!.string shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier"
 
         // delete all tests
         coronaTests.value = emptySet()
 
-        censor.checkLog(filterMe) shouldBe filterMe.copy(
-            message = "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier"
-        )
+        censor.checkLog(filterMe)!!.string shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier"
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt
index fead49fe9..5f7b72554 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.censors.contactdiary.DiaryEncounterCensor
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPersonEncounter
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
 import io.kotest.matchers.shouldBe
@@ -51,51 +50,35 @@ class DiaryEncounterCensorTest : BaseTest() {
         )
 
         val instance = createInstance(this)
-        val censorMe = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message =
-                """
-                On A rainy day,
-                two persons Spilled coffee on each others laptops,
-                everyone disliked that.
-                """.trimIndent(),
-            tag = "I'm a tag",
-            throwable = null
-        )
-
-        instance.checkLog(censorMe) shouldBe censorMe.copy(
-            message =
-                """
-                    On Encounter#2/Circumstances,
-                    two persons Encounter#3/Circumstances,
-                    everyone disliked that.
-                """.trimIndent()
-        )
+        val censorMe =
+            """
+            On A rainy day,
+            two persons Spilled coffee on each others laptops,
+            everyone disliked that.
+            """.trimIndent()
+
+        instance.checkLog(censorMe)!!.string shouldBe
+            """
+            On Encounter#2/Circumstances,
+            two persons Encounter#3/Circumstances,
+            everyone disliked that.
+            """.trimIndent()
 
         // censoring should still work after encounters are deleted
         every { diaryRepo.personEncounters } returns flowOf(emptyList())
-        instance.checkLog(censorMe) shouldBe censorMe.copy(
-            message =
-                """
-                    On Encounter#2/Circumstances,
-                    two persons Encounter#3/Circumstances,
-                    everyone disliked that.
-                """.trimIndent()
-        )
+        instance.checkLog(censorMe)!!.string shouldBe
+            """
+            On Encounter#2/Circumstances,
+            two persons Encounter#3/Circumstances,
+            everyone disliked that.
+            """.trimIndent()
     }
 
     @Test
     fun `censoring returns null if all circumstances are blank`() = runBlockingTest {
         every { diaryRepo.personEncounters } returns flowOf(listOf(mockEncounter(1, _circumstances = "")))
         val instance = createInstance(this)
-        val notCensored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "That was strange.",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val notCensored = "That was strange."
         instance.checkLog(notCensored) shouldBe null
     }
 
@@ -105,13 +88,7 @@ class DiaryEncounterCensorTest : BaseTest() {
 
         val instance = createInstance(this)
 
-        val notCensored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Nothing ever happens.",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val notCensored = "Nothing ever happens."
         instance.checkLog(notCensored) shouldBe null
     }
 
@@ -125,13 +102,7 @@ class DiaryEncounterCensorTest : BaseTest() {
         )
 
         val instance = createInstance(this)
-        val notCensored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "I like turtles",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val notCensored = "I like turtles"
         instance.checkLog(notCensored) shouldBe null
     }
 
@@ -145,13 +116,7 @@ class DiaryEncounterCensorTest : BaseTest() {
             )
         )
 
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
 
         var isFinished = false
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt
index 191afcd5d..c708da370 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.censors.contactdiary.DiaryLocationCensor
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
 import io.kotest.matchers.shouldBe
@@ -54,50 +53,34 @@ class DiaryLocationCensorTest : BaseTest() {
             )
         )
         val instance = createInstance(this)
-        val censorMe = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message =
-                """
-                Bürgermeister of Munich (+49 089 3333) and Karl of Aachen [+49 0241 9999] called each other.
-                Both agreed that their emails (bürgermeister@münchen.de|karl@aachen.de) are awesome,
-                and that Bielefeld doesn't exist as it has neither phonenumber (null) nor email (null).
-                """.trimIndent(),
-            tag = "I'm a tag",
-            throwable = null
-        )
-        instance.checkLog(censorMe) shouldBe censorMe.copy(
-            message =
-                """
-                Bürgermeister of Location#1/Name (Location#1/PhoneNumber) and Karl of Location#3/Name [Location#3/PhoneNumber] called each other.
-                Both agreed that their emails (Location#1/EMail|Location#3/EMail) are awesome,
-                and that Location#2/Name doesn't exist as it has neither phonenumber (null) nor email (null).
-                """.trimIndent()
-        )
+        val censorMe =
+            """
+            Bürgermeister of Munich (+49 089 3333) and Karl of Aachen [+49 0241 9999] called each other.
+            Both agreed that their emails (bürgermeister@münchen.de|karl@aachen.de) are awesome,
+            and that Bielefeld doesn't exist as it has neither phonenumber (null) nor email (null).
+            """.trimIndent()
+        instance.checkLog(censorMe)!!.string shouldBe
+            """
+            Bürgermeister of Location#1/Name (Location#1/PhoneNumber) and Karl of Location#3/Name [Location#3/PhoneNumber] called each other.
+            Both agreed that their emails (Location#1/EMail|Location#3/EMail) are awesome,
+            and that Location#2/Name doesn't exist as it has neither phonenumber (null) nor email (null).
+            """.trimIndent()
 
         // censoring should still work after locations are deleted
         every { diaryRepo.locations } returns flowOf(emptyList())
-        instance.checkLog(censorMe) shouldBe censorMe.copy(
-            message =
-                """
-                Bürgermeister of Location#1/Name (Location#1/PhoneNumber) and Karl of Location#3/Name [Location#3/PhoneNumber] called each other.
-                Both agreed that their emails (Location#1/EMail|Location#3/EMail) are awesome,
-                and that Location#2/Name doesn't exist as it has neither phonenumber (null) nor email (null).
-                """.trimIndent()
-        )
+        instance.checkLog(censorMe)!!.string shouldBe
+            """
+            Bürgermeister of Location#1/Name (Location#1/PhoneNumber) and Karl of Location#3/Name [Location#3/PhoneNumber] called each other.
+            Both agreed that their emails (Location#1/EMail|Location#3/EMail) are awesome,
+            and that Location#2/Name doesn't exist as it has neither phonenumber (null) nor email (null).
+            """.trimIndent()
     }
 
     @Test
     fun `censoring returns null if there are no locations no match`() = runBlockingTest {
         every { diaryRepo.locations } returns flowOf(emptyList())
         val instance = createInstance(this)
-        val notCensored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Can't visit many cities during lockdown...",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val notCensored = "Can't visit many cities during lockdown..."
         instance.checkLog(notCensored) shouldBe null
     }
 
@@ -111,13 +94,7 @@ class DiaryLocationCensorTest : BaseTest() {
             )
         )
         val instance = createInstance(this)
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
         instance.checkLog(logLine) shouldBe null
     }
 
@@ -132,13 +109,7 @@ class DiaryLocationCensorTest : BaseTest() {
             )
         )
 
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
 
         var isFinished = false
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt
index 1025df09c..240c80f3c 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.censors.contactdiary.DiaryPersonCensor
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPerson
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
 import io.kotest.matchers.shouldBe
@@ -55,50 +54,34 @@ class DiaryPersonCensorTest : BaseTest() {
             )
         )
         val instance = createInstance(this)
-        val censorMe = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message =
-                """
-                Ralf requested more coffee from +49 1234 7777,
-                but Matthias thought he had enough has had enough for today.
-                A quick mail to luka@sap.com confirmed this.
-                """.trimIndent(),
-            tag = "I'm a tag",
-            throwable = null
-        )
-        instance.checkLog(censorMe) shouldBe censorMe.copy(
-            message =
-                """
-                Person#2/Name requested more coffee from Person#1/PhoneNumber,
-                but Person#3/Name thought he had enough has had enough for today.
-                A quick mail to Person#1/EMail confirmed this.
-                """.trimIndent()
-        )
+        val censorMe =
+            """
+            Ralf requested more coffee from +49 1234 7777,
+            but Matthias thought he had enough has had enough for today.
+            A quick mail to luka@sap.com confirmed this.
+            """.trimIndent()
+        instance.checkLog(censorMe)!!.string shouldBe
+            """
+            Person#2/Name requested more coffee from Person#1/PhoneNumber,
+            but Person#3/Name thought he had enough has had enough for today.
+            A quick mail to Person#1/EMail confirmed this.
+            """.trimIndent()
 
         // censoring should still work after people are deleted
         every { diaryRepo.people } returns flowOf(emptyList())
-        instance.checkLog(censorMe) shouldBe censorMe.copy(
-            message =
-                """
-                Person#2/Name requested more coffee from Person#1/PhoneNumber,
-                but Person#3/Name thought he had enough has had enough for today.
-                A quick mail to Person#1/EMail confirmed this.
-                """.trimIndent()
-        )
+        instance.checkLog(censorMe)!!.string shouldBe
+            """
+            Person#2/Name requested more coffee from Person#1/PhoneNumber,
+            but Person#3/Name thought he had enough has had enough for today.
+            A quick mail to Person#1/EMail confirmed this.
+            """.trimIndent()
     }
 
     @Test
     fun `censoring returns null if there are no persons no match`() = runBlockingTest {
         every { diaryRepo.people } returns flowOf(emptyList())
         val instance = createInstance(this)
-        val notCensored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "May 2021 be better than 2020.",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val notCensored = "May 2021 be better than 2020."
         instance.checkLog(notCensored) shouldBe null
     }
 
@@ -112,13 +95,7 @@ class DiaryPersonCensorTest : BaseTest() {
             )
         )
         val instance = createInstance(this)
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
         instance.checkLog(logLine) shouldBe null
     }
 
@@ -133,13 +110,7 @@ class DiaryPersonCensorTest : BaseTest() {
             )
         )
 
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
 
         var isFinished = false
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt
index 5c8a0ed51..cdd31e2a5 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.censors.contactdiary.DiaryVisitCensor
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocationVisit
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
 import io.kotest.matchers.shouldBe
@@ -50,50 +49,34 @@ class DiaryVisitCensorTest : BaseTest() {
             )
         )
         val instance = createInstance(this)
-        val censorMe = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message =
-                """
-                After having a Döner that was too spicy,
-                I got my beard shaved without mask,
-                only to find out the supermarket was out of toiletpaper.
-                """.trimIndent(),
-            tag = "I'm a tag",
-            throwable = null
-        )
-        instance.checkLog(censorMe) shouldBe censorMe.copy(
-            message =
-                """
-                After having a Visit#1/Circumstances,
-                I got my Visit#2/Circumstances,
-                only to find out the supermarket was Visit#3/Circumstances.
-                """.trimIndent()
-        )
+        val censorMe =
+            """
+            After having a Döner that was too spicy,
+            I got my beard shaved without mask,
+            only to find out the supermarket was out of toiletpaper.
+            """.trimIndent()
+        instance.checkLog(censorMe)!!.string shouldBe
+            """
+            After having a Visit#1/Circumstances,
+            I got my Visit#2/Circumstances,
+            only to find out the supermarket was Visit#3/Circumstances.
+            """.trimIndent()
 
         // censoring should still work even after visits are deleted
         every { diaryRepo.locationVisits } returns flowOf(emptyList())
-        instance.checkLog(censorMe) shouldBe censorMe.copy(
-            message =
-                """
-                After having a Visit#1/Circumstances,
-                I got my Visit#2/Circumstances,
-                only to find out the supermarket was Visit#3/Circumstances.
-                """.trimIndent()
-        )
+        instance.checkLog(censorMe)!!.string shouldBe
+            """
+            After having a Visit#1/Circumstances,
+            I got my Visit#2/Circumstances,
+            only to find out the supermarket was Visit#3/Circumstances.
+            """.trimIndent()
     }
 
     @Test
     fun `censoring returns null if all circumstances are blank`() = runBlockingTest {
         every { diaryRepo.locationVisits } returns flowOf(listOf(mockVisit(1, _circumstances = "")))
         val instance = createInstance(this)
-        val notCensored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "So many places to visit, but no place like home!",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val notCensored = "So many places to visit, but no place like home!"
         instance.checkLog(notCensored) shouldBe null
     }
 
@@ -101,13 +84,7 @@ class DiaryVisitCensorTest : BaseTest() {
     fun `censoring returns null if there are no visits no match`() = runBlockingTest {
         every { diaryRepo.locationVisits } returns flowOf(emptyList())
         val instance = createInstance(this)
-        val notCensored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "So many places to visit, but no place like home!",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val notCensored = "So many places to visit, but no place like home!"
         instance.checkLog(notCensored) shouldBe null
     }
 
@@ -120,13 +97,7 @@ class DiaryVisitCensorTest : BaseTest() {
             )
         )
         val instance = createInstance(this)
-        val notCensored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Wakey wakey, eggs and bakey.",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val notCensored = "Wakey wakey, eggs and bakey."
         instance.checkLog(notCensored) shouldBe null
     }
 
@@ -140,13 +111,7 @@ class DiaryVisitCensorTest : BaseTest() {
             )
         )
 
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
 
         var isFinished = false
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/PcrQrCodeCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/PcrQrCodeCensorTest.kt
index 58826f8ff..f7551bb70 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/PcrQrCodeCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/PcrQrCodeCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.censors.submission.PcrQrCodeCensor
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import kotlinx.coroutines.test.runBlockingTest
@@ -30,29 +29,15 @@ class PcrQrCodeCensorTest : BaseTest() {
     fun `censoring replaces the logline message`() = runBlockingTest {
         PcrQrCodeCensor.lastGUID = testGUID
         val instance = createInstance()
-        val censored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "I'm a shy qrcode: $testGUID",
-            tag = "I'm a tag",
-            throwable = null
-        )
-        instance.checkLog(censored) shouldBe censored.copy(
-            message = "I'm a shy qrcode: ########-####-####-####-########3a2f"
-        )
+        val censored = "I'm a shy qrcode: $testGUID"
+        instance.checkLog(censored)!!.string shouldBe "I'm a shy qrcode: ########-####-####-####-########3a2f"
     }
 
     @Test
     fun `censoring returns null if there is no match`() = runBlockingTest {
         PcrQrCodeCensor.lastGUID = testGUID.replace("f", "a")
         val instance = createInstance()
-        val notCensored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "I'm a shy qrcode: $testGUID",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val notCensored = "I'm a shy qrcode: $testGUID"
         instance.checkLog(notCensored) shouldBe null
     }
 
@@ -60,13 +45,7 @@ class PcrQrCodeCensorTest : BaseTest() {
     fun `censoring aborts if no qrcode was set`() = runBlockingTest {
         PcrQrCodeCensor.lastGUID = null
         val instance = createInstance()
-        val notCensored = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "I'm a shy qrcode: $testGUID",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val notCensored = "I'm a shy qrcode: $testGUID"
         instance.checkLog(notCensored) shouldBe null
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RACoronaTestCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RACoronaTestCensorTest.kt
index 74c66acfa..3a4e06d93 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RACoronaTestCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RACoronaTestCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.censors.submission.RACoronaTestCensor
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest
 import io.kotest.matchers.shouldBe
@@ -45,33 +44,23 @@ internal class RACoronaTestCensorTest : BaseTest() {
 
         val censor = createInstance(this)
 
-        val logLineToCensor = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message =
-                """
-                Hello! My name is John. My friends call me Mister Doe and I was born on 2020-01-01.
-                """.trimIndent(),
-            tag = "I am tag",
-            throwable = null
-        )
+        val logLineToCensor =
+            """
+            Hello! My name is John. My friends call me Mister Doe and I was born on 2020-01-01.
+            """.trimIndent()
 
-        censor.checkLog(logLineToCensor) shouldBe logLineToCensor.copy(
-            message =
-                """
-                Hello! My name is RATest/FirstName. My friends call me Mister RATest/LastName and I was born on RATest/DateOfBirth.
-                """.trimIndent()
-        )
+        censor.checkLog(logLineToCensor)!!.string shouldBe
+            """
+            Hello! My name is RATest/FirstName. My friends call me Mister RATest/LastName and I was born on RATest/DateOfBirth.
+            """.trimIndent()
 
         // censoring should still work when test gets deleted
         every { coronaTestRepository.coronaTests } returns flowOf(emptySet())
 
-        censor.checkLog(logLineToCensor) shouldBe logLineToCensor.copy(
-            message =
-                """
-                Hello! My name is RATest/FirstName. My friends call me Mister RATest/LastName and I was born on RATest/DateOfBirth.
-                """.trimIndent()
-        )
+        censor.checkLog(logLineToCensor)!!.string shouldBe
+            """
+            Hello! My name is RATest/FirstName. My friends call me Mister RATest/LastName and I was born on RATest/DateOfBirth.
+            """.trimIndent()
     }
 
     @Test
@@ -80,13 +69,7 @@ internal class RACoronaTestCensorTest : BaseTest() {
 
         val censor = createInstance(this)
 
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
 
         censor.checkLog(logLine) shouldBe null
     }
@@ -105,13 +88,7 @@ internal class RACoronaTestCensorTest : BaseTest() {
 
         val censor = createInstance(this)
 
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
         censor.checkLog(logLine) shouldBe null
     }
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RatQrCodeCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RatQrCodeCensorTest.kt
index 6689f4e48..afef21806 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RatQrCodeCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RatQrCodeCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.censors.submission.RatQrCodeCensor
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import kotlinx.coroutines.test.runBlockingTest
@@ -39,30 +38,17 @@ internal class RatQrCodeCensorTest {
 
         val censor = createInstance()
 
-        val logLineToCensor = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Here comes the hash: $testHash of the rat test of Milhouse Van Houten. He was born on 1980-07-01",
-            tag = "I am tag",
-            throwable = null
-        )
+        val logLineToCensor =
+            "Here comes the hash: $testHash of the rat test of Milhouse Van Houten. He was born on 1980-07-01"
 
-        censor.checkLog(logLineToCensor) shouldBe logLineToCensor.copy(
-            message = "Here comes the hash: SHA256HASH-ENDING-WITH-15ad of the rat test of RATest/FirstName RATest/LastName. He was born on RATest/DateOfBirth"
-        )
+        censor.checkLog(logLineToCensor)!!.string shouldBe "Here comes the hash: SHA256HASH-ENDING-WITH-15ad of the rat test of RATest/FirstName RATest/LastName. He was born on RATest/DateOfBirth"
     }
 
     @Test
     fun `checkLog() should return null if no data to censor was set`() = runBlockingTest {
         val censor = createInstance()
 
-        val logLineNotToCensor = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Here comes the hash: $testHash",
-            tag = "I am tag",
-            throwable = null
-        )
+        val logLineNotToCensor = "Here comes the hash: $testHash"
 
         censor.checkLog(logLineNotToCensor) shouldBe null
     }
@@ -79,13 +65,7 @@ internal class RatQrCodeCensorTest {
 
         val censor = createInstance()
 
-        val logLineNotToCensor = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Here comes the hash: $testHash",
-            tag = "I am tag",
-            throwable = null
-        )
+        val logLineNotToCensor = "Here comes the hash: $testHash"
 
         censor.checkLog(logLineNotToCensor) shouldBe null
     }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/TraceLocationCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/TraceLocationCensorTest.kt
index c7f6d6b7f..92bd63b96 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/TraceLocationCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/TraceLocationCensorTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.censors.presencetracing.TraceLocationCensor
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.presencetracing.checkins.qrcode.TraceLocation
 import de.rki.coronawarnapp.presencetracing.locations.TraceLocationUserInput
 import de.rki.coronawarnapp.presencetracing.storage.repo.TraceLocationRepository
@@ -72,36 +71,26 @@ internal class TraceLocationCensorTest : BaseTest() {
 
         val censor = createInstance(this)
 
-        val logLineToCensor = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message =
-                """
-                The type is LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT. Yesterday we went to the Rick Astley Concert. The spectacle took place in Never gonna give you up street 1, 12345 RickRoll City. 
-                Afterwards we had some food in Sushi Place in Sushi Street 123, 12345 Fish Town. It a nice LOCATION_TYPE_PERMANENT_FOOD_SERVICE.
-                """.trimIndent(),
-            tag = "I am tag",
-            throwable = null
-        )
+        val logLineToCensor =
+            """
+            The type is LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT. Yesterday we went to the Rick Astley Concert. The spectacle took place in Never gonna give you up street 1, 12345 RickRoll City. 
+            Afterwards we had some food in Sushi Place in Sushi Street 123, 12345 Fish Town. It a nice LOCATION_TYPE_PERMANENT_FOOD_SERVICE.
+            """.trimIndent()
 
-        censor.checkLog(logLineToCensor) shouldBe logLineToCensor.copy(
-            message =
-                """
-                The type is TraceLocation#2/Type. Yesterday we went to the TraceLocation#2/Description. The spectacle took place in TraceLocation#2/Address. 
-                Afterwards we had some food in TraceLocation#1/Description in TraceLocation#1/Address. It a nice TraceLocation#1/Type.
-                """.trimIndent()
-        )
+        censor.checkLog(logLineToCensor)!!.string shouldBe
+            """
+            The type is TraceLocation#2/Type. Yesterday we went to the TraceLocation#2/Description. The spectacle took place in TraceLocation#2/Address. 
+            Afterwards we had some food in TraceLocation#1/Description in TraceLocation#1/Address. It a nice TraceLocation#1/Type.
+            """.trimIndent()
 
         // censoring should still work after the user deletes his trace locations
         every { traceLocationRepo.allTraceLocations } returns flowOf(emptyList())
 
-        censor.checkLog(logLineToCensor) shouldBe logLineToCensor.copy(
-            message =
-                """
-                The type is TraceLocation#2/Type. Yesterday we went to the TraceLocation#2/Description. The spectacle took place in TraceLocation#2/Address. 
-                Afterwards we had some food in TraceLocation#1/Description in TraceLocation#1/Address. It a nice TraceLocation#1/Type.
-                """.trimIndent()
-        )
+        censor.checkLog(logLineToCensor)!!.string shouldBe
+            """
+            The type is TraceLocation#2/Type. Yesterday we went to the TraceLocation#2/Description. The spectacle took place in TraceLocation#2/Address. 
+            Afterwards we had some food in TraceLocation#1/Description in TraceLocation#1/Address. It a nice TraceLocation#1/Type.
+            """.trimIndent()
     }
 
     @Test
@@ -119,25 +108,17 @@ internal class TraceLocationCensorTest : BaseTest() {
 
             val censor = createInstance(this)
 
-            val logLineToCensor = LogLine(
-                timestamp = 1,
-                priority = 3,
-                message =
-                    """
+            val logLineToCensor =
+                """
                 The user just created a new traceLocation with Top Secret Private Event as the description and
                 top secret address as the address. The type is LOCATION_TYPE_TEMPORARY_PRIVATE_EVENT. 
-                    """.trimIndent(),
-                tag = "I am tag",
-                throwable = null
-            )
+                """.trimIndent()
 
-            censor.checkLog(logLineToCensor) shouldBe logLineToCensor.copy(
-                message =
-                    """
+            censor.checkLog(logLineToCensor)!!.string shouldBe
+                """
                 The user just created a new traceLocation with TraceLocationUserInput#Description as the description and
                 TraceLocationUserInput#Address as the address. The type is TraceLocationUserInput#Type. 
-                    """.trimIndent()
-            )
+                """.trimIndent()
         }
 
     @Test
@@ -145,13 +126,7 @@ internal class TraceLocationCensorTest : BaseTest() {
         every { traceLocationRepo.allTraceLocations } returns flowOf(emptyList())
 
         val censor = createInstance(this)
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
         censor.checkLog(logLine) shouldBe null
     }
 
@@ -176,13 +151,7 @@ internal class TraceLocationCensorTest : BaseTest() {
         )
 
         val censor = createInstance(this)
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
 
         censor.checkLog(logLine) shouldBe null
     }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensorTest.kt
index 9093882a6..2134750ac 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensorTest.kt
@@ -1,6 +1,5 @@
 package de.rki.coronawarnapp.bugreporting.censors.submission
 
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.coronatest.antigen.profile.RATProfile
 import de.rki.coronawarnapp.coronatest.antigen.profile.RATProfileSettings
 import io.kotest.matchers.shouldBe
@@ -33,13 +32,7 @@ internal class RatProfileCensorTest : BaseTest() {
 
         val censor = createInstance()
 
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "Lorem ipsum"
 
         censor.checkLog(logLine) shouldBe null
     }
@@ -50,13 +43,7 @@ internal class RatProfileCensorTest : BaseTest() {
 
         val censor = createInstance()
 
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Lorem ipsum",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine = "I'm a tag"
 
         censor.checkLog(logLine) shouldBe null
     }
@@ -67,27 +54,20 @@ internal class RatProfileCensorTest : BaseTest() {
 
         val censor = createInstance()
 
-        val logLine = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Mister First name who is also known as Last name and is born on 1950-08-01 lives in Main street, " +
-                "12132 in the beautiful city of London. You can reach him by phone: 111111111 or email: email@example.com",
-            tag = "I'm a tag",
-            throwable = null
-        )
+        val logLine =
+            "Mister First name who is also known as Last name and is born on 1950-08-01 lives in Main street, " +
+                "12132 in the beautiful city of London. You can reach him by phone: 111111111 or email: email@example.com"
 
-        censor.checkLog(logLine) shouldBe logLine.copy(
-            message = "Mister RAT-Profile/FirstName who is also known as RAT-Profile/LastName and is born on RAT-Profile/DateOfBirth lives in RAT-Profile/Street, " +
-                "RAT-Profile/Zip-Code in the beautiful city of RAT-Profile/City. You can reach him by phone: RAT-Profile/Phone or email: RAT-Profile/eMail"
-        )
+        censor.checkLog(logLine)!!.string shouldBe
+            "Mister RAT-Profile/FirstName who is also known as RAT-Profile/LastName and is born on RAT-Profile/DateOfBirth lives in RAT-Profile/Street, " +
+            "RAT-Profile/Zip-Code in the beautiful city of RAT-Profile/City. You can reach him by phone: RAT-Profile/Phone or email: RAT-Profile/eMail"
 
         // censoring should still work after the user deletes his profile
         every { ratProfileSettings.profile.flow } returns flowOf(null)
 
-        censor.checkLog(logLine) shouldBe logLine.copy(
-            message = "Mister RAT-Profile/FirstName who is also known as RAT-Profile/LastName and is born on RAT-Profile/DateOfBirth lives in RAT-Profile/Street, " +
-                "RAT-Profile/Zip-Code in the beautiful city of RAT-Profile/City. You can reach him by phone: RAT-Profile/Phone or email: RAT-Profile/eMail"
-        )
+        censor.checkLog(logLine)!!.string shouldBe
+            "Mister RAT-Profile/FirstName who is also known as RAT-Profile/LastName and is born on RAT-Profile/DateOfBirth lives in RAT-Profile/Street, " +
+            "RAT-Profile/Zip-Code in the beautiful city of RAT-Profile/City. You can reach him by phone: RAT-Profile/Phone or email: RAT-Profile/eMail"
     }
 
     private val formatter = DateTimeFormat.forPattern("yyyy-MM-dd")
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensorTest.kt
index e6c292712..a5509d80b 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensorTest.kt
@@ -1,6 +1,5 @@
 package de.rki.coronawarnapp.bugreporting.censors.vaccination
 
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1
 import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateData
 import io.kotest.matchers.shouldBe
@@ -63,49 +62,27 @@ internal class CertificateQrCodeCensorTest {
 
         val censor = createInstance()
 
-        val logLineToCensor = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Here comes the rawString: $testRawString of the vaccine certificate",
-            tag = "I am tag",
-            throwable = null
-        )
+        val logLineToCensor = "Here comes the rawString: $testRawString of the vaccine certificate"
 
-        censor.checkLog(logLineToCensor) shouldBe logLineToCensor.copy(
-            message = "Here comes the rawString: ########-####-####-####-########C\$AH of the vaccine certificate",
-        )
+        censor.checkLog(logLineToCensor)!!.string shouldBe "Here comes the rawString: ########-####-####-####-########C\$AH of the vaccine certificate"
 
-        val certDataToCensor = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Hello my name is Kevin Bob, i was born at 1969-11-16, i have been " +
-                "vaccinated with: 12345 1214765 aaEd/easd ASD-2312 1969-04-20 DE Herbert" +
-                " urn:uvci:01:NL:PlA8UWS60Z4RZXVALl6GAZ",
-            tag = "I am tag",
-            throwable = null
-        )
+        val certDataToCensor = "Hello my name is Kevin Bob, i was born at 1969-11-16, i have been " +
+            "vaccinated with: 12345 1214765 aaEd/easd ASD-2312 1969-04-20 DE Herbert" +
+            " urn:uvci:01:NL:PlA8UWS60Z4RZXVALl6GAZ"
 
-        censor.checkLog(certDataToCensor) shouldBe certDataToCensor.copy(
-            message = "Hello my name is nameData/familyName nameData/givenName, i was born at " +
-                "vaccinationCertificate/dob, i have been vaccinated with: vaccinationData/targetId " +
-                "vaccinationData/vaccineId vaccinationData/medicalProductId" +
-                " vaccinationData/marketAuthorizationHolderId vaccinationData/dt" +
-                " vaccinationData/countryOfVaccination vaccinationData/certificateIssuer" +
-                " vaccinationData/uniqueCertificateIdentifier"
-        )
+        censor.checkLog(certDataToCensor)!!.string shouldBe "Hello my name is nameData/familyName nameData/givenName, i was born at " +
+            "vaccinationCertificate/dob, i have been vaccinated with: vaccinationData/targetId " +
+            "vaccinationData/vaccineId vaccinationData/medicalProductId" +
+            " vaccinationData/marketAuthorizationHolderId vaccinationData/dt" +
+            " vaccinationData/countryOfVaccination vaccinationData/certificateIssuer" +
+            " vaccinationData/uniqueCertificateIdentifier"
     }
 
     @Test
     fun `checkLog() should return null if no data to censor was set`() = runBlockingTest {
         val censor = createInstance()
 
-        val logLineNotToCensor = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Here comes the rawData: $testRawString",
-            tag = "I am tag",
-            throwable = null
-        )
+        val logLineNotToCensor = "Here comes the rawData: $testRawString"
 
         censor.checkLog(logLineNotToCensor) shouldBe null
     }
@@ -117,13 +94,7 @@ internal class CertificateQrCodeCensorTest {
 
         val censor = createInstance()
 
-        val logLineNotToCensor = LogLine(
-            timestamp = 1,
-            priority = 3,
-            message = "Here comes the rawString: $testRawString",
-            tag = "I am tag",
-            throwable = null
-        )
+        val logLineNotToCensor = "Here comes the rawString: $testRawString"
 
         censor.checkLog(logLineNotToCensor) shouldBe null
     }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt
index 029f51ee5..24b467db1 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt
@@ -2,6 +2,8 @@ package de.rki.coronawarnapp.bugreporting.debuglog
 
 import android.app.Application
 import dagger.Lazy
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
 import de.rki.coronawarnapp.bugreporting.censors.submission.CoronaTestCensor
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLogTree
 import de.rki.coronawarnapp.util.CWADebug
@@ -29,7 +31,8 @@ class DebugLoggerTest : BaseIOTest() {
 
     @MockK lateinit var application: Application
     @MockK lateinit var component: ApplicationComponent
-    @MockK lateinit var coronaTestCensor: CoronaTestCensor
+    @MockK lateinit var coronaTestCensor1: CoronaTestCensor
+    @MockK lateinit var coronaTestCensor2: CoronaTestCensor
 
     private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!)
     private val cacheDir = File(testDir, "cache")
@@ -49,10 +52,11 @@ class DebugLoggerTest : BaseIOTest() {
         every { application.cacheDir } returns cacheDir
         every { component.inject(any<DebugLogger>()) } answers {
             val logger = arg<DebugLogger>(0)
-            logger.bugCensors = Lazy { setOf(coronaTestCensor) }
+            logger.bugCensors = Lazy { setOf(coronaTestCensor1, coronaTestCensor2) }
         }
 
-        coEvery { coronaTestCensor.checkLog(any()) } returns null
+        coEvery { coronaTestCensor1.checkLog(any()) } returns null
+        coEvery { coronaTestCensor2.checkLog(any()) } returns null
     }
 
     @AfterEach
@@ -178,19 +182,15 @@ class DebugLoggerTest : BaseIOTest() {
             setInjectionIsReady(component)
         }
 
-        runBlockingTest {
-            instance.start()
+        instance.start()
 
-            Timber.tag("Tag123").v("Message456")
-            advanceTimeBy(2000L)
+        Timber.tag("Tag123").v("Message456")
+        advanceTimeBy(2000L)
 
-            runningLog.readLines().last().substring(26) shouldBe """
-                V/Tag123: Message456
-            """.trimIndent()
+        runningLog.readLines().last().substring(25) shouldBe "V/Tag123: Message456"
 
-            instance.stop()
-            advanceUntilIdle()
-        }
+        instance.stop()
+        advanceUntilIdle()
     }
 
     @Test
@@ -214,7 +214,7 @@ class DebugLoggerTest : BaseIOTest() {
         testCollector.latestValue shouldBe LogState(
             isLogging = true,
             isLowStorage = false,
-            logSize = 78L
+            logSize = 77L
         )
 
         instance.stop()
@@ -228,4 +228,39 @@ class DebugLoggerTest : BaseIOTest() {
 
         testCollector.cancel()
     }
+
+    @Test
+    fun `affected text ranges are removed when censoring collisions occur`() = runBlockingTest {
+        val instance = createInstance(scope = this).apply {
+            init()
+            setInjectionIsReady(component)
+        }
+
+        val logMsg = "Lukas says: A hot coffee is really nice!"
+
+        coEvery { coronaTestCensor1.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            BugCensor.CensoredString(msg).censor("says: A hot coffee", "says: A hot tea")
+        }
+
+        instance.start()
+
+        Timber.tag("Test").v(logMsg)
+        advanceTimeBy(2000L)
+
+        runningLog.readLines().last().substring(25) shouldBe "V/Test: Lukas says: A hot tea is really nice!"
+
+        coEvery { coronaTestCensor2.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            BugCensor.CensoredString(msg).censor("says:", "sings:")
+        }
+
+        Timber.tag("Test").v(logMsg)
+        advanceTimeBy(2000L)
+
+        runningLog.readLines().last().substring(25) shouldBe "V/Test: Lukas <censoring-collision> is really nice!"
+
+        instance.stop()
+        advanceUntilIdle()
+    }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLineTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLineTest.kt
new file mode 100644
index 000000000..50b05b41b
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLineTest.kt
@@ -0,0 +1,39 @@
+package de.rki.coronawarnapp.bugreporting.debuglog
+
+import android.util.Log
+import de.rki.coronawarnapp.util.trimToLength
+import io.kotest.matchers.shouldBe
+import okio.IOException
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class LogLineTest : BaseTest() {
+
+    @Test
+    fun `log formatting`() {
+        LogLine(
+            timestamp = 123L,
+            priority = Log.ERROR,
+            tag = "IamATag",
+            message = "Low storage check failed.",
+            throwable = null
+        ).format() shouldBe """
+            1970-01-01T00:00:00.123Z E/IamATag: Low storage check failed.
+        """.trimIndent()
+    }
+
+    @Test
+    fun `log formatting with error`() {
+        LogLine(
+            timestamp = 123L,
+            priority = Log.ERROR,
+            tag = "IamATag",
+            message = "Low storage check failed.",
+            throwable = IOException()
+        ).format().trimToLength(182) shouldBe """
+            1970-01-01T00:00:00.123Z E/IamATag: Low storage check failed.
+            java.io.IOException
+            	at de.rki.coronawarnapp.bugreporting.debuglog.LogLineTest.log formatting with error(LogLineTest.kt:
+        """.trimIndent()
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLogStorageCheckTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLogStorageCheckTest.kt
index f19da6396..d8d72156f 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLogStorageCheckTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/DebugLogStorageCheckTest.kt
@@ -1,16 +1,19 @@
 package de.rki.coronawarnapp.bugreporting.debuglog.internal
 
-import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
 import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
 import io.mockk.Called
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
+import io.mockk.coEvery
+import io.mockk.coVerify
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import io.mockk.just
 import io.mockk.mockk
 import io.mockk.slot
 import io.mockk.verify
+import kotlinx.coroutines.test.runBlockingTest
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
@@ -22,7 +25,7 @@ class DebugLogStorageCheckTest : BaseTest() {
     @MockK lateinit var targetPath: File
     @MockK lateinit var logWriter: LogWriter
 
-    private var currentTime: Long = 5001L
+    private var ourTime: Long = 5001L
 
     @BeforeEach
     fun setup() {
@@ -31,7 +34,7 @@ class DebugLogStorageCheckTest : BaseTest() {
         every { targetPath.usableSpace } returns 250 * 1000 * 1024L
         every { targetPath.parentFile } returns null
         every { targetPath.exists() } returns true
-        every { logWriter.write(any()) } just Runs
+        coEvery { logWriter.write(any()) } just Runs
     }
 
     @AfterEach
@@ -40,12 +43,12 @@ class DebugLogStorageCheckTest : BaseTest() {
 
     private fun createInstance() = DebugLogStorageCheck(
         targetPath = targetPath,
-        timeProvider = { currentTime },
+        timeProvider = { ourTime },
         logWriter = logWriter
     )
 
     @Test
-    fun `normal not low storage case`() {
+    fun `normal not low storage case`() = runBlockingTest {
         val instance = createInstance()
         instance.isLowStorage() shouldBe false
 
@@ -53,34 +56,37 @@ class DebugLogStorageCheckTest : BaseTest() {
     }
 
     @Test
-    fun `on errors we print it but do expect low storage`() {
-        val unexpectedError = Exception("ಠ_ಠ")
-        every { targetPath.usableSpace } throws unexpectedError
+    fun `on errors we print it but do expect low storage`() = runBlockingTest {
+        val unexpectedException = IllegalThreadStateException("ಠ_ಠ")
+        every { targetPath.usableSpace } throws unexpectedException
 
-        val logSlot = slot<LogLine>()
-        every { logWriter.write(capture(logSlot)) } just Runs
+        val logSlot = slot<String>()
+        coEvery { logWriter.write(capture(logSlot)) } just Runs
 
         val instance = createInstance()
         instance.isLowStorage() shouldBe true
 
-        logSlot.captured.throwable shouldBe unexpectedError
+        logSlot.captured.apply {
+            this shouldContain "ಠ_ಠ"
+            this shouldContain "IllegalThreadStateException"
+        }
     }
 
     @Test
-    fun `low storage default is 200MB`() {
+    fun `low storage default is 200MB`() = runBlockingTest {
         every { targetPath.usableSpace } returns 199 * 1000 * 1024L
         val instance = createInstance()
         instance.isLowStorage() shouldBe true
 
-        currentTime += 60 * 1000L
+        ourTime += 60 * 1000L
         instance.isLowStorage() shouldBe true
 
         // We only write the warning once
-        verify(exactly = 1) { logWriter.write(any()) }
+        coVerify(exactly = 1) { logWriter.write(any()) }
     }
 
     @Test
-    fun `target path does not exists`() {
+    fun `target path does not exists`() = runBlockingTest {
         val parentPath = mockk<File>()
         every { parentPath.exists() } returns true
         every { parentPath.parentFile } returns null
@@ -97,7 +103,7 @@ class DebugLogStorageCheckTest : BaseTest() {
     }
 
     @Test
-    fun `checks happen at most every 5 seconds`() {
+    fun `checks happen at most every 5 seconds`() = runBlockingTest {
         val instance = createInstance()
         instance.isLowStorage() shouldBe false
 
@@ -107,7 +113,7 @@ class DebugLogStorageCheckTest : BaseTest() {
 
         verify(exactly = 1) { targetPath.usableSpace }
 
-        currentTime += 5000L
+        ourTime += 5000L
 
         instance.isLowStorage() shouldBe true
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/LogSnapshotterTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/LogSnapshotterTest.kt
index 0bba493b4..475ec34f8 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/LogSnapshotterTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/internal/LogSnapshotterTest.kt
@@ -8,7 +8,8 @@ import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
-import org.joda.time.DateTime
+import org.joda.time.Instant
+import org.joda.time.format.DateTimeFormat
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
@@ -27,7 +28,9 @@ class LogSnapshotterTest : BaseIOTest() {
     private val runningLogFake = File(testDir, "running.log")
 
     private val snapshotDir = File(cacheDir, "debuglog_snapshots")
-    private val expectedSnapshot = File(snapshotDir, "CWA Log 1970-01-01 00_00_00.000.zip")
+    private val fileNameDateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH_mm_ss.SSS")
+    private val userTime = Instant.EPOCH.toUserTimeZone()
+    private val expectedSnapshot = File(snapshotDir, "CWA Log ${userTime.toString(fileNameDateFormatter)}.zip")
 
     @BeforeEach
     fun setup() {
@@ -39,7 +42,7 @@ class LogSnapshotterTest : BaseIOTest() {
         testDir.exists() shouldBe true
 
         every { debugLogger.runningLog } returns runningLogFake
-        every { timeStamper.nowUTC.toUserTimeZone() } returns DateTime.parse("1970-01-01T00:00:00.000Z")
+        every { timeStamper.nowUTC } returns userTime.toInstant()
 
         runningLogFake.parentFile!!.mkdirs()
         runningLogFake.writeText("1 Doge = 1 Doge")
-- 
GitLab