From e12bb0576e5f184fbc8ed89676b5650f0485eba1 Mon Sep 17 00:00:00 2001
From: Lukas Lechner <lukas.lechner@sap.com>
Date: Thu, 27 May 2021 16:20:06 +0200
Subject: [PATCH] Improve log censoring mechanics (EXPOSUREAPP-7455,
 EXPOSUREAPP-7196, EXPOSUREAPP-7514) (#3280)

* Catch IndexOutOfBoundsExceptions in the DebugLogger and log out information

* Fix IndexOutOfBoundsException due to censored string bounds exceeding original log message.

* Fix missing censoring if log lines contain the same value multiple times.

* Remove unused code.

* Adjust CensoredString.censor(...) to determine the bounds always on the original.

* Add test for incorrectly censored message due to changed bounds (working with bounds on the censored message, instead of original message).

* Implement internal censoring collision handling within each censoring module.

* Remove superfluous factory method.

* Move the final censor compilation step to the DebugLogger, in case of collisions we don't need to do all the work.
Re-use censor container for "per module" censoring and the "top-level" combination of all modules.

* Adjust censoring for values that need to be censored, but do not overlap, each other, just touch: "<ABC><DEF>".

* LINTs

* Don't pass all meta data to censoring, we only need to censor the line, the metadata can be added later. No unnecessary string work.

* Fix typos and docs.

* Remove coerce as we work only the original.

* Implement equals/hashcode for replacement actions and changed collection from LIST to SET, to prevent collisions due to duplicate replacement actions.

* If a censor throws an exception, censor everything as precaution.

* Fix flaky test.

Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com>
Co-authored-by: BMItter <Berndus@gmx.de>
Co-authored-by: Matthias Urhahn <matthias.urhahn@sap.com>
---
 .../bugreporting/censors/BugCensor.kt         | 130 ++++++---
 .../contactdiary/DiaryEncounterCensor.kt      |  13 +-
 .../contactdiary/DiaryLocationCensor.kt       |  17 +-
 .../censors/contactdiary/DiaryPersonCensor.kt |  17 +-
 .../censors/contactdiary/DiaryVisitCensor.kt  |  13 +-
 .../censors/presencetracing/CheckInsCensor.kt |  15 +-
 .../presencetracing/TraceLocationCensor.kt    |  23 +-
 .../censors/submission/CoronaTestCensor.kt    |  15 +-
 .../censors/submission/PcrQrCodeCensor.kt     |   7 +-
 .../censors/submission/RACoronaTestCensor.kt  |  17 +-
 .../censors/submission/RatProfileCensor.kt    |  27 +-
 .../censors/submission/RatQrCodeCensor.kt     |  21 +-
 .../vaccination/CertificateQrCodeCensor.kt    |  76 ++---
 .../bugreporting/debuglog/DebugLogger.kt      |  32 ++-
 .../bugreporting/debuglog/LogLine.kt          |  17 +-
 .../src/test/java/android/util/Log.java       |  39 +++
 .../bugreporting/censors/BugCensorTest.kt     | 151 +++++++++-
 .../censors/CensorInjectionTest.kt            |   8 +-
 .../censors/CheckInsCensorTest.kt             |  14 +-
 .../censors/CoronaTestCensorTest.kt           |   9 +-
 .../censors/DiaryEncounterCensorTest.kt       |   4 +-
 .../censors/DiaryLocationCensorTest.kt        |   4 +-
 .../censors/DiaryPersonCensorTest.kt          |   4 +-
 .../censors/DiaryVisitCensorTest.kt           |   4 +-
 .../censors/PcrQrCodeCensorTest.kt            |   3 +-
 .../censors/RACoronaTestCensorTest.kt         |   4 +-
 .../censors/RatQrCodeCensorTest.kt            |   3 +-
 .../censors/TraceLocationCensorTest.kt        |  59 ++--
 .../submission/RatProfileCensorTest.kt        |  22 +-
 .../CertificateQrCodeCensorTest.kt            |  14 +-
 .../bugreporting/debuglog/DebugLoggerTest.kt  | 260 +++++++++++++++++-
 .../bugreporting/debuglog/LogLineTest.kt      |  26 +-
 32 files changed, 787 insertions(+), 281 deletions(-)
 create mode 100644 Corona-Warn-App/src/test/java/android/util/Log.java

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 57cf1c85a..49754b1bc 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,47 +1,98 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
-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(message: String): CensoredString?
+    suspend fun checkLog(message: String): CensorContainer?
 
-    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
-    )
+    data class CensorContainer(
+        // Original String, necessary for correct censoring ranges
+        val original: String,
+        val actions: Set<Action> = emptySet()
+    ) {
 
-    companion object {
-        operator fun CensoredString.plus(newer: CensoredString?): CensoredString {
-            if (newer == null) return this
+        fun censor(toReplace: String, replacement: String): CensorContainer {
+            if (!original.contains(toReplace)) return this
+
+            val start = original.indexOf(toReplace)
+            if (start == -1) return this // Shouldn't happen
+
+            val end = original.lastIndexOf(toReplace) + toReplace.length
 
-            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)
+            val newAction = Action(
+                range = start..end,
+                modifier = Action.SimpleReplace(toReplace, replacement)
+            )
+            return this.copy(actions = actions.plus(newAction))
+        }
+
+        fun compile(): CensoredString? {
+            val ranges = actions.map { it.range }
+            if (ranges.isEmpty()) return null
+
+            val isIntersecting = ranges.any { outer ->
+                ranges.any { inner ->
+                    outer !== inner &&
+                        (inner.contains(outer.first) || inner.contains(outer.last)) &&
+                        (inner.last != outer.first && inner.first != outer.last)
+                }
             }
 
-            return CensoredString(string = newer.string, range = range)
+            return if (isIntersecting) {
+                val minMin = ranges.minOf { it.first }
+                val maxMax = ranges.maxOf { it.last }
+                CensoredString(
+                    censored = original.replaceRange(minMin, maxMax, COLLISION_STRING),
+                    ranges = listOf(minMin..maxMax)
+                )
+            } else {
+                CensoredString(
+                    censored = actions.fold(original) { notOriginal, action ->
+                        action.execute(notOriginal)
+                    },
+                    ranges = ranges
+                )
+            }
         }
 
-        fun CensoredString.censor(orig: String, replacement: String): CensoredString? {
-            val start = this.string.indexOf(orig)
-            if (start == -1) return null
+        fun nullIfEmpty(): CensorContainer? = if (actions.isEmpty()) null else this
 
-            val end = start + orig.length
-            return CensoredString(
-                string = this.string.replace(orig, replacement),
-                range = start..end
-            )
+        data class Action(
+            val range: IntRange,
+            val modifier: StringModifier,
+        ) {
+
+            fun execute(original: String) = modifier.execute(original)
+
+            data class SimpleReplace(
+                val oldValue: String,
+                val newValue: String,
+            ) : StringModifier {
+                override val execute: (String) -> String = { it.replace(oldValue, newValue) }
+            }
+
+            interface StringModifier {
+                val execute: (String) -> String
+            }
         }
 
+        companion object {
+            const val COLLISION_STRING = "<censor-collision/>"
+
+            fun createErrorContainer(censor: BugCensor, exception: Exception): CensorContainer {
+                return CensorContainer("<censor-error>$censor: $exception</censor-error>")
+            }
+        }
+    }
+
+    data class CensoredString(
+        // The censored version of the string
+        val censored: String,
+        // The range that we censored
+        // If there is a collision, this range in the original needs to be removed.
+        val ranges: List<IntRange>
+    )
+
+    companion object {
         fun withValidName(name: String?, action: (String) -> Unit): Boolean {
             if (name.isNullOrBlank()) return false
             if (name.length < 3) return false
@@ -98,8 +149,21 @@ interface BugCensor {
             return true
         }
 
-        fun CensoredString.toNullIfUnmodified(): CensoredString? {
-            return if (range == null) null else this
-        }
+        fun containerForError(
+            censor: BugCensor,
+            original: String,
+            error: Exception
+        ): CensorContainer = CensorContainer(
+            original = original,
+            actions = setOf(
+                CensorContainer.Action(
+                    range = 0..original.length,
+                    modifier = CensorContainer.Action.SimpleReplace(
+                        original,
+                        "<censor-error>Module ${censor.javaClass.simpleName}: $error</censor-error>"
+                    )
+                )
+            )
+        )
     }
 }
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 63c6cfc42..876966ae9 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,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidComment
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPersonEncounter
@@ -34,20 +31,20 @@ class DiaryEncounterCensor @Inject constructor(
             .launchIn(debugScope)
     }
 
-    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
+    override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock {
 
         if (encounterHistory.isEmpty()) return null
 
-        val newMessage = encounterHistory.fold(CensoredString(message)) { orig, encounter ->
+        val newMessage = encounterHistory.fold(CensorContainer(message)) { orig, encounter ->
             var wip = orig
 
             withValidComment(encounter.circumstances) {
-                wip += wip.censor(it, "Encounter#${encounter.id}/Circumstances")
+                wip = wip.censor(it, "Encounter#${encounter.id}/Circumstances")
             }
 
             wip
         }
 
-        return newMessage.toNullIfUnmodified()
+        return newMessage.nullIfEmpty()
     }
 }
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 e1439a50c..73d7c9f77 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,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 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
@@ -35,26 +32,26 @@ class DiaryLocationCensor @Inject constructor(
             .launchIn(debugScope)
     }
 
-    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
+    override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock {
 
         if (locationHistory.isEmpty()) return null
 
-        val newMessage = locationHistory.fold(CensoredString(message)) { orig, location ->
+        val newMessage = locationHistory.fold(CensorContainer(message)) { orig, location ->
             var wip = orig
 
             withValidName(location.locationName) {
-                wip += wip.censor(it, "Location#${location.locationId}/Name")
+                wip = wip.censor(it, "Location#${location.locationId}/Name")
             }
             withValidEmail(location.emailAddress) {
-                wip += wip.censor(it, "Location#${location.locationId}/EMail")
+                wip = wip.censor(it, "Location#${location.locationId}/EMail")
             }
             withValidPhoneNumber(location.phoneNumber) {
-                wip += wip.censor(it, "Location#${location.locationId}/PhoneNumber")
+                wip = wip.censor(it, "Location#${location.locationId}/PhoneNumber")
             }
 
             wip
         }
 
-        return newMessage.toNullIfUnmodified()
+        return newMessage.nullIfEmpty()
     }
 }
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 52f542fc4..aa8125f34 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,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 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
@@ -36,26 +33,26 @@ class DiaryPersonCensor @Inject constructor(
             .launchIn(debugScope)
     }
 
-    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
+    override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock {
 
         if (personHistory.isEmpty()) return null
 
-        val newMessage = personHistory.fold(CensoredString(message)) { orig, person ->
+        val newMessage = personHistory.fold(CensorContainer(message)) { orig, person ->
             var wip = orig
 
             withValidName(person.fullName) {
-                wip += wip.censor(it, "Person#${person.personId}/Name")
+                wip = wip.censor(it, "Person#${person.personId}/Name")
             }
             withValidEmail(person.emailAddress) {
-                wip += wip.censor(it, "Person#${person.personId}/EMail")
+                wip = wip.censor(it, "Person#${person.personId}/EMail")
             }
             withValidPhoneNumber(person.phoneNumber) {
-                wip += wip.censor(it, "Person#${person.personId}/PhoneNumber")
+                wip = wip.censor(it, "Person#${person.personId}/PhoneNumber")
             }
 
             wip
         }
 
-        return newMessage.toNullIfUnmodified()
+        return newMessage.nullIfEmpty()
     }
 }
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 f08bfc69d..4a6bf335f 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,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocationVisit
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
@@ -37,20 +34,20 @@ class DiaryVisitCensor @Inject constructor(
             .launchIn(debugScope)
     }
 
-    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
+    override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock {
 
         if (visitsHistory.isEmpty()) return null
 
-        val newMessage = visitsHistory.fold(CensoredString(message)) { orig, visit ->
+        val newMessage = visitsHistory.fold(CensorContainer(message)) { orig, visit ->
             var wip = orig
 
             BugCensor.withValidComment(visit.circumstances) {
-                wip += wip.censor(it, "Visit#${visit.id}/Circumstances")
+                wip = wip.censor(it, "Visit#${visit.id}/Circumstances")
             }
 
             wip
         }
 
-        return newMessage.toNullIfUnmodified()
+        return newMessage.nullIfEmpty()
     }
 }
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 7cbede14a..e342c3caa 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,7 @@ package de.rki.coronawarnapp.bugreporting.censors.presencetracing
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidAddress
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidDescription
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
@@ -34,25 +31,25 @@ class CheckInsCensor @Inject constructor(
             .launchIn(debugScope)
     }
 
-    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
+    override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock {
 
         if (checkInsHistory.isEmpty()) return null
 
-        val newLogMsg = checkInsHistory.fold(CensoredString(message)) { initial, checkIn ->
+        val newLogMsg = checkInsHistory.fold(CensorContainer(message)) { initial, checkIn ->
 
             var acc = initial
 
             withValidDescription(checkIn.description) { description ->
-                acc += acc.censor(description, "CheckIn#${checkIn.id}/Description")
+                acc = acc.censor(description, "CheckIn#${checkIn.id}/Description")
             }
 
             withValidAddress(checkIn.address) { address ->
-                acc += acc.censor(address, "CheckIn#${checkIn.id}/Address")
+                acc = acc.censor(address, "CheckIn#${checkIn.id}/Address")
             }
 
             acc
         }
 
-        return newLogMsg.toNullIfUnmodified()
+        return newLogMsg.nullIfEmpty()
     }
 }
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 cae77e407..3b58cdfd4 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,7 @@ package de.rki.coronawarnapp.bugreporting.censors.presencetracing
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidAddress
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidDescription
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
@@ -43,19 +40,19 @@ class TraceLocationCensor @Inject constructor(
             .launchIn(debugScope)
     }
 
-    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
+    override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock {
 
-        var newLogMsg = traceLocationHistory.fold(CensoredString(message)) { initial, traceLocation ->
+        var newLogMsg = traceLocationHistory.fold(CensorContainer(message)) { initial, traceLocation ->
             var acc = initial
 
-            acc += acc.censor(traceLocation.type.name, "TraceLocation#${traceLocation.id}/Type")
+            acc = acc.censor(traceLocation.type.name, "TraceLocation#${traceLocation.id}/Type")
 
             withValidDescription(traceLocation.description) { description ->
-                acc += acc.censor(description, "TraceLocation#${traceLocation.id}/Description")
+                acc = acc.censor(description, "TraceLocation#${traceLocation.id}/Description")
             }
 
             withValidAddress(traceLocation.address) { address ->
-                acc += acc.censor(address, "TraceLocation#${traceLocation.id}/Address")
+                acc = acc.censor(address, "TraceLocation#${traceLocation.id}/Address")
             }
 
             acc
@@ -63,18 +60,18 @@ class TraceLocationCensor @Inject constructor(
 
         val inputDataToCensor = dataToCensor
         if (inputDataToCensor != null) {
-            newLogMsg += newLogMsg.censor(inputDataToCensor.type.name, "TraceLocationUserInput#Type")
+            newLogMsg = newLogMsg.censor(inputDataToCensor.type.name, "TraceLocationUserInput#Type")
 
             withValidDescription(inputDataToCensor.description) {
-                newLogMsg += newLogMsg.censor(inputDataToCensor.description, "TraceLocationUserInput#Description")
+                newLogMsg = newLogMsg.censor(inputDataToCensor.description, "TraceLocationUserInput#Description")
             }
 
             withValidAddress(inputDataToCensor.address) {
-                newLogMsg += newLogMsg.censor(inputDataToCensor.address, "TraceLocationUserInput#Address")
+                newLogMsg = newLogMsg.censor(inputDataToCensor.address, "TraceLocationUserInput#Address")
             }
         }
 
-        return newLogMsg.toNullIfUnmodified()
+        return newLogMsg.nullIfEmpty()
     }
 
     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 ea9d31a4f..611232d11 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,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.submission
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import kotlinx.coroutines.CoroutineScope
@@ -40,23 +37,23 @@ class CoronaTestCensor @Inject constructor(
             }.launchIn(debugScope)
     }
 
-    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
+    override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock {
 
-        var newMessage = CensoredString(message)
+        var newMessage = CensorContainer(message)
 
         for (token in tokenHistory) {
             if (!message.contains(token)) continue
 
-            newMessage += newMessage.censor(token, PLACEHOLDER + token.takeLast(4))
+            newMessage = newMessage.censor(token, PLACEHOLDER + token.takeLast(4))
         }
 
         identifierHistory
             .filter { message.contains(it) }
             .forEach {
-                newMessage += newMessage.censor(it, "${it.take(11)}CoronaTest/Identifier")
+                newMessage = newMessage.censor(it, "${it.take(11)}CoronaTest/Identifier")
             }
 
-        return newMessage.toNullIfUnmodified()
+        return newMessage.nullIfEmpty()
     }
 
     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 0c61dd0ca..190773e03 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,18 +2,17 @@ package de.rki.coronawarnapp.bugreporting.censors.submission
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString
-import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer
 import javax.inject.Inject
 
 @Reusable
 class PcrQrCodeCensor @Inject constructor() : BugCensor {
 
-    override suspend fun checkLog(message: String): CensoredString? {
+    override suspend fun checkLog(message: String): CensorContainer? {
         val guid = lastGUID ?: return null
         if (!message.contains(guid)) return null
 
-        return CensoredString(message).censor(guid, PLACEHOLDER + guid.takeLast(4))
+        return CensorContainer(message).censor(guid, PLACEHOLDER + guid.takeLast(4)).nullIfEmpty()
     }
 
     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 b2eff3186..e40f7085c 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,10 +1,7 @@
 package de.rki.coronawarnapp.bugreporting.censors.submission
 
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
 import de.rki.coronawarnapp.coronatest.CoronaTestRepository
@@ -38,24 +35,24 @@ class RACoronaTestCensor @Inject constructor(
             .launchIn(debugScope)
     }
 
-    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
+    override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock {
 
-        var newMessage = CensoredString(message)
+        var newMessage = CensorContainer(message)
 
         ratCoronaTestHistory.forEach { ratCoronaTest ->
             withValidName(ratCoronaTest.firstName) { firstName ->
-                newMessage += newMessage.censor(firstName, "RATest/FirstName")
+                newMessage = newMessage.censor(firstName, "RATest/FirstName")
             }
 
             withValidName(ratCoronaTest.lastName) { lastName ->
-                newMessage += newMessage.censor(lastName, "RATest/LastName")
+                newMessage = newMessage.censor(lastName, "RATest/LastName")
             }
 
             ratCoronaTest.dateOfBirth?.toString(dayOfBirthFormatter)?.let { dateOfBirthString ->
-                newMessage += newMessage.censor(dateOfBirthString, "RATest/DateOfBirth")
+                newMessage = newMessage.censor(dateOfBirthString, "RATest/DateOfBirth")
             }
         }
 
-        return newMessage.toNullIfUnmodified()
+        return newMessage.nullIfEmpty()
     }
 }
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 b33838de5..71b9eaae0 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,10 +1,7 @@
 package de.rki.coronawarnapp.bugreporting.censors.submission
 
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 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
@@ -26,7 +23,7 @@ class RatProfileCensor @Inject constructor(
     private val dayOfBirthFormatter = DateTimeFormat.forPattern("yyyy-MM-dd")
     private val ratProfileHistory = mutableSetOf<RATProfile>()
 
-    override suspend fun checkLog(message: String): CensoredString? = mutex.withLock {
+    override suspend fun checkLog(message: String): CensorContainer? = 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
@@ -34,46 +31,46 @@ class RatProfileCensor @Inject constructor(
             ratProfileHistory.add(ratProfile)
         }
 
-        var newMessage = CensoredString(message)
+        var newMessage = CensorContainer(message)
 
         ratProfileHistory.forEach { profile ->
             with(profile) {
                 withValidName(firstName) { firstName ->
-                    newMessage += newMessage.censor(firstName, "RAT-Profile/FirstName")
+                    newMessage = newMessage.censor(firstName, "RAT-Profile/FirstName")
                 }
 
                 withValidName(lastName) { lastName ->
-                    newMessage += newMessage.censor(lastName, "RAT-Profile/LastName")
+                    newMessage = newMessage.censor(lastName, "RAT-Profile/LastName")
                 }
 
                 val dateOfBirthString = birthDate?.toString(dayOfBirthFormatter)
 
                 if (dateOfBirthString != null) {
-                    newMessage += newMessage.censor(dateOfBirthString, "RAT-Profile/DateOfBirth")
+                    newMessage = newMessage.censor(dateOfBirthString, "RAT-Profile/DateOfBirth")
                 }
 
                 withValidAddress(street) { street ->
-                    newMessage += newMessage.censor(street, "RAT-Profile/Street")
+                    newMessage = newMessage.censor(street, "RAT-Profile/Street")
                 }
 
                 withValidCity(city) { city ->
-                    newMessage += newMessage.censor(city, "RAT-Profile/City")
+                    newMessage = newMessage.censor(city, "RAT-Profile/City")
                 }
 
                 withValidZipCode(zipCode) { zipCode ->
-                    newMessage += newMessage.censor(zipCode, "RAT-Profile/Zip-Code")
+                    newMessage = newMessage.censor(zipCode, "RAT-Profile/Zip-Code")
                 }
 
                 withValidPhoneNumber(phone) { phone ->
-                    newMessage += newMessage.censor(phone, "RAT-Profile/Phone")
+                    newMessage = newMessage.censor(phone, "RAT-Profile/Phone")
                 }
 
                 withValidPhoneNumber(email) { phone ->
-                    newMessage += newMessage.censor(phone, "RAT-Profile/eMail")
+                    newMessage = newMessage.censor(phone, "RAT-Profile/eMail")
                 }
             }
         }
 
-        return newMessage.toNullIfUnmodified()
+        return newMessage.nullIfEmpty()
     }
 }
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 f7537dc8d..def0f07a0 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,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.submission
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName
 import de.rki.coronawarnapp.coronatest.qrcode.RapidAntigenHash
 import org.joda.time.LocalDate
@@ -17,31 +14,31 @@ class RatQrCodeCensor @Inject constructor() : BugCensor {
 
     private val dayOfBirthFormatter = DateTimeFormat.forPattern("yyyy-MM-dd")
 
-    override suspend fun checkLog(message: String): CensoredString? {
+    override suspend fun checkLog(message: String): CensorContainer? {
 
         val dataToCensor = dataToCensor ?: return null
 
-        var newMessage = CensoredString(message)
+        var newMessage = CensorContainer(message)
 
         with(dataToCensor) {
-            newMessage += newMessage.censor(rawString, "RatQrCode/ScannedRawString")
+            newMessage = newMessage.censor(rawString, "RatQrCode/ScannedRawString")
 
-            newMessage += newMessage.censor(hash, PLACEHOLDER + hash.takeLast(4))
+            newMessage = newMessage.censor(hash, PLACEHOLDER + hash.takeLast(4))
 
             withValidName(firstName) { firstName ->
-                newMessage += newMessage.censor(firstName, "RATest/FirstName")
+                newMessage = newMessage.censor(firstName, "RATest/FirstName")
             }
 
             withValidName(lastName) { lastName ->
-                newMessage += newMessage.censor(lastName, "RATest/LastName")
+                newMessage = newMessage.censor(lastName, "RATest/LastName")
             }
 
             val dateOfBirthString = dateOfBirth?.toString(dayOfBirthFormatter) ?: return@with
 
-            newMessage += newMessage.censor(dateOfBirthString, "RATest/DateOfBirth")
+            newMessage = newMessage.censor(dateOfBirthString, "RATest/DateOfBirth")
         }
 
-        return newMessage.toNullIfUnmodified()
+        return newMessage.nullIfEmpty()
     }
 
     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 3b1dbda24..83c22418c 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,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.vaccination
 
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
-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.CensorContainer
 import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1
 import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateData
 import java.util.LinkedList
@@ -14,114 +11,119 @@ import javax.inject.Inject
 @Reusable
 class CertificateQrCodeCensor @Inject constructor() : BugCensor {
 
-    override suspend fun checkLog(message: String): CensoredString? {
-        var newMessage = CensoredString(message)
+    override suspend fun checkLog(message: String): CensorContainer? {
+        var newMessage = CensorContainer(message)
 
         synchronized(qrCodeStringsToCensor) { qrCodeStringsToCensor.toList() }.forEach {
-            newMessage += newMessage.censor(it, PLACEHOLDER + it.takeLast(4))
+            newMessage = newMessage.censor(it, PLACEHOLDER + it.takeLast(4))
         }
 
         synchronized(certsToCensor) { certsToCensor.toList() }.forEach {
             it.certificate.apply {
-                newMessage += newMessage.censor(
-                    dob,
-                    "vaccinationCertificate/dob"
-                )
+                val dobFormatted = dateOfBirth.toString()
 
-                newMessage += newMessage.censor(
-                    dateOfBirth.toString(),
+                newMessage = newMessage.censor(
+                    dobFormatted,
                     "vaccinationCertificate/dateOfBirth"
                 )
+                if (dobFormatted != dob) {
+                    newMessage = newMessage.censor(
+                        dob,
+                        "vaccinationCertificate/dob"
+                    )
+                }
 
-                newMessage += censorNameData(nameData, newMessage)
+                newMessage = censorNameData(nameData, newMessage)
 
                 vaccinationDatas.forEach { data ->
-                    newMessage += censorVaccinationData(data, newMessage)
+                    newMessage = censorVaccinationData(data, newMessage)
                 }
             }
         }
 
-        return newMessage.toNullIfUnmodified()
+        return newMessage.nullIfEmpty()
     }
 
     private fun censorVaccinationData(
         vaccinationData: VaccinationDGCV1.VaccinationData,
-        message: CensoredString
-    ): CensoredString {
+        message: CensorContainer
+    ): CensorContainer {
         var newMessage = message
 
-        newMessage += newMessage.censor(
-            vaccinationData.dt,
-            "vaccinationData/dt"
-        )
-
-        newMessage += newMessage.censor(
+        newMessage = newMessage.censor(
             vaccinationData.marketAuthorizationHolderId,
             "vaccinationData/marketAuthorizationHolderId"
         )
 
-        newMessage += newMessage.censor(
+        newMessage = newMessage.censor(
             vaccinationData.medicalProductId,
             "vaccinationData/medicalProductId"
         )
 
-        newMessage += newMessage.censor(
+        newMessage = newMessage.censor(
             vaccinationData.targetId,
             "vaccinationData/targetId"
         )
 
-        newMessage += newMessage.censor(
+        newMessage = newMessage.censor(
             vaccinationData.certificateIssuer,
             "vaccinationData/certificateIssuer"
         )
 
-        newMessage += newMessage.censor(
+        newMessage = newMessage.censor(
             vaccinationData.uniqueCertificateIdentifier,
             "vaccinationData/uniqueCertificateIdentifier"
         )
 
-        newMessage += newMessage.censor(
+        newMessage = newMessage.censor(
             vaccinationData.countryOfVaccination,
             "vaccinationData/countryOfVaccination"
         )
 
-        newMessage += newMessage.censor(
+        newMessage = newMessage.censor(
             vaccinationData.vaccineId,
             "vaccinationData/vaccineId"
         )
 
-        newMessage += newMessage.censor(
-            vaccinationData.vaccinatedAt.toString(),
+        val vaccinatedAt = vaccinationData.vaccinatedAt.toString()
+        newMessage = newMessage.censor(
+            vaccinatedAt,
             "vaccinationData/vaccinatedAt"
         )
+        if (vaccinatedAt != vaccinationData.dt) {
+            newMessage = newMessage.censor(
+                vaccinationData.dt,
+                "vaccinationData/dt"
+            )
+        }
 
         return newMessage
     }
 
-    private fun censorNameData(nameData: VaccinationDGCV1.NameData, message: CensoredString): CensoredString {
+    private fun censorNameData(nameData: VaccinationDGCV1.NameData, message: CensorContainer): CensorContainer {
         var newMessage = message
 
         nameData.familyName?.let { fName ->
-            newMessage += newMessage.censor(
+            newMessage = newMessage.censor(
                 fName,
                 "nameData/familyName"
             )
         }
 
-        newMessage += newMessage.censor(
+        newMessage = newMessage.censor(
             nameData.familyNameStandardized,
             "nameData/familyNameStandardized"
         )
 
         nameData.givenName?.let { gName ->
-            newMessage += newMessage.censor(
+            newMessage = newMessage.censor(
                 gName,
                 "nameData/givenName"
             )
         }
 
         nameData.givenNameStandardized?.let { gName ->
-            newMessage += newMessage.censor(
+            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 5fe3b904e..ab7f0f1f6 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
@@ -165,26 +165,37 @@ class DebugLogger(
                     delay(1000)
 
                     val formattedMessage = rawLine.format()
-                    val censored: Collection<BugCensor.CensoredString> = bugCensors.get()
+                    val censored: Collection<BugCensor.CensorContainer> = bugCensors.get()
                         .map {
                             async {
-                                it.checkLog(formattedMessage)
+                                try {
+                                    it.checkLog(formattedMessage)
+                                } catch (e: Exception) {
+                                    Log.e(TAG, "Error in censor module $it", e)
+                                    BugCensor.containerForError(it, formattedMessage, e)
+                                }
                             }
                         }
                         .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)
-                        }
+                        1 -> censored.single().compile()?.censored ?: formattedMessage
+                        else ->
+                            try {
+                                val combinedContainer = BugCensor.CensorContainer(
+                                    original = formattedMessage,
+                                    actions = censored.flatMap { it.actions }.toSet()
+                                )
+
+                                combinedContainer.compile()?.censored ?: formattedMessage
+                            } catch (e: Exception) {
+                                Log.e(TAG, "Censoring collision fail.", e)
+                                "<censor-error>Global combination: $e</censor-error>"
+                            }
                     }
-                    logWriter.write(toWrite)
+                    logWriter.write(rawLine.formatFinal(toWrite))
                 }
             }
         } catch (e: CancellationException) {
@@ -195,7 +206,6 @@ 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 67664ffba..260dd10d4 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
@@ -13,16 +13,15 @@ data class LogLine(
     val throwable: Throwable?
 ) {
 
-    fun format(): String {
-        val time = Instant.ofEpochMilli(timestamp)
-
-        val baseLine = "$time ${priorityLabel(priority)}/$tag: $message"
+    fun format(): String = if (throwable != null) {
+        message + "\n" + getStackTraceString(throwable)
+    } else {
+        message
+    }
 
-        return if (throwable != null) {
-            baseLine + "\n" + getStackTraceString(throwable)
-        } else {
-            baseLine
-        }
+    fun formatFinal(formattedLogLine: String): String {
+        val time = Instant.ofEpochMilli(timestamp)
+        return "$time ${priorityLabel(priority)}/$tag: $formattedLogLine"
     }
 
     companion object {
diff --git a/Corona-Warn-App/src/test/java/android/util/Log.java b/Corona-Warn-App/src/test/java/android/util/Log.java
new file mode 100644
index 000000000..3ec4706ce
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/android/util/Log.java
@@ -0,0 +1,39 @@
+package android.util;
+
+public class Log {
+
+    public static final int VERBOSE = 2;
+    public static final int DEBUG = 3;
+    public static final int INFO = 4;
+    public static final int WARN = 5;
+    public static final int ERROR = 6;
+    public static final int ASSERT = 7;
+
+    public static int d(String tag, String msg) {
+        System.out.println("D: " + tag + ": " + msg);
+        return 0;
+    }
+
+    public static int i(String tag, String msg) {
+        System.out.println("I/: " + tag + ": " + msg);
+        return 0;
+    }
+
+    public static int w(String tag, String msg) {
+        System.out.println("W/: " + tag + ": " + msg);
+        return 0;
+    }
+
+    public static int e(String tag, String msg) {
+        return e(tag, msg, null);
+    }
+
+    public static int e(String tag, String msg, Throwable error) {
+        if (error != null) {
+            System.out.println("E/: " + tag + ": " + msg + "Error: " + error.toString());
+        } else {
+            System.out.println("E/: " + tag + ": " + msg);
+        }
+        return 0;
+    }
+}
\ No newline at end of file
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 bb6d1f2f7..1d4228cba 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,6 +1,5 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
-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
@@ -99,8 +98,152 @@ class BugCensorTest : BaseTest() {
     }
 
     @Test
-    fun `cesnsor string is nulled if not modified`() {
-        BugCensor.CensoredString("abc", 1..2).toNullIfUnmodified() shouldNotBe null
-        BugCensor.CensoredString("abc", null).toNullIfUnmodified() shouldBe null
+    fun `censor string is nulled if not modified`() {
+        BugCensor.CensorContainer("abc").compile() shouldBe null
+        BugCensor.CensorContainer("abc").censor("abc", "123").compile() shouldNotBe null
+        BugCensor.CensorContainer("abc").censor("123", "abc").compile() shouldBe null
+    }
+
+    @Test
+    fun `censoring range determination`() {
+        val input = "1234567890ABCDEFG"
+        val one = BugCensor.CensorContainer(input)
+
+        one.censor("1234", "").apply {
+            actions.single().apply {
+                range shouldBe 0..4
+                execute(original) shouldBe "567890ABCDEFG"
+            }
+        }
+
+        one.censor("1234", "....").apply {
+            actions.single().apply {
+                execute(original) shouldBe "....567890ABCDEFG"
+                range shouldBe 0..4
+            }
+        }
+
+        one.censor("DEFG", "....").apply {
+            actions.single().apply {
+                execute(original) shouldBe "1234567890ABC...."
+                range shouldBe 13..(13 + 4)
+            }
+        }
+
+        one.censor("1234567890ABCDEFG", "...").apply {
+            actions.single().apply {
+                execute(original) shouldBe "..."
+                range shouldBe 0..(0 + 17)
+            }
+        }
+
+        one.censor("#", "...").actions shouldBe emptyList()
+
+        one.censor("1234567890ABCDEFG", "1234567890ABCDEFG###").apply {
+            actions.single().apply {
+                execute(original) shouldBe "1234567890ABCDEFG###"
+                range shouldBe 0..(0 + 17)
+            }
+        }
+
+        one.censor("", " ").apply {
+            actions.single().apply {
+                execute(original) shouldBe " 1 2 3 4 5 6 7 8 9 0 A B C D E F G "
+                range shouldBe 0..16
+            }
+        }
+    }
+
+    @Test
+    fun `censoring range combination`() {
+        val container1 = BugCensor.CensorContainer("abcdefg")
+        container1.actions shouldBe emptyList()
+        val container2 = container1.censor("cde", "345")
+        container2.actions.map { it.range }.toSet() shouldBe setOf(2..5)
+        val container3 = container2.censor("ab", "12")
+        container3.actions.map { it.range }.toSet() shouldBe setOf(0..2, 2..5)
+    }
+
+    @Test
+    fun `censoring disjoint`() {
+        BugCensor.CensorContainer("#abcdefg*")
+            .censor("abc", "123")
+            .censor("efg", "567")
+            .compile()!!
+            .apply {
+                censored shouldBe "#123d567*"
+                ranges shouldBe setOf(1..4, 5..8)
+            }
+    }
+
+    @Test
+    fun `censoring disjoint - touching`() {
+        BugCensor.CensorContainer("#abcefg*")
+            .censor("abc", "123")
+            .censor("efg", "567")
+            .compile()!!
+            .apply {
+                censored shouldBe "#123567*"
+                ranges shouldBe setOf(1..4, 4..7)
+            }
+    }
+
+    @Test
+    fun `censoring overlap`() {
+        BugCensor.CensorContainer("#abcdefg*")
+            .censor("abcd", "1234")
+            .censor("defg", "4567")
+            .compile()!!
+            .apply {
+                censored shouldBe "#<censor-collision/>*"
+                ranges shouldBe setOf(1..8)
+            }
+    }
+
+    @Test
+    fun `censoring complete overlap`() {
+        BugCensor.CensorContainer("#abcdefg*")
+            .censor("abc", "---")
+            .censor("abc", "+++")
+            .compile()!!
+            .apply {
+                censored shouldBe "#<censor-collision/>defg*"
+                ranges shouldBe setOf(1..4, 1..4)
+            }
+    }
+
+    @Test
+    fun `full replacement collision`() {
+        BugCensor.CensorContainer("#abcdefg*")
+            .censor("#abcdefg*", "#1234567*")
+            .censor("#abcdefg*", "#*")
+            .compile()!!
+            .apply {
+                censored shouldBe "<censor-collision/>"
+                ranges shouldBe setOf(0..9, 0..9)
+            }
+    }
+
+    @Test
+    fun `nested replacement collision`() {
+        BugCensor.CensorContainer("#abcdefg*")
+            .censor("#abcdefg*", "#abcdefg*")
+            .censor("abcdefg", "abcdefg")
+            .compile()!!
+            .apply {
+                censored shouldBe "<censor-collision/>"
+                ranges shouldBe setOf(0..9)
+            }
+    }
+
+    @Test
+    fun `string length boundary check`() {
+        BugCensor.CensorContainer("#abcdefg*")
+            .censor("*", "**")
+            .compile()!!
+            .apply {
+                censored shouldBe "#abcdefg**"
+                ranges shouldBe setOf(8..9)
+            }
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt
index 915ca4bb0..cc4c29283 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt
@@ -69,7 +69,7 @@ class MockProvider {
 
     @Singleton
     @Provides
-    fun diary(): ContactDiaryRepository = mockk() {
+    fun diary(): ContactDiaryRepository = mockk {
         every { people } returns flowOf(emptyList())
         every { personEncounters } returns flowOf(emptyList())
         every { locations } returns flowOf(emptyList())
@@ -82,19 +82,19 @@ class MockProvider {
 
     @Singleton
     @Provides
-    fun coronaTestRepository(): CoronaTestRepository = mockk() {
+    fun coronaTestRepository(): CoronaTestRepository = mockk {
         every { coronaTests } returns flowOf(emptySet())
     }
 
     @Singleton
     @Provides
-    fun checkInRepository(): CheckInRepository = mockk() {
+    fun checkInRepository(): CheckInRepository = mockk {
         every { allCheckIns } returns flowOf(emptyList())
     }
 
     @Singleton
     @Provides
-    fun traceLocationRepository(): TraceLocationRepository = mockk() {
+    fun traceLocationRepository(): TraceLocationRepository = mockk {
         every { allTraceLocations } returns flowOf(emptyList())
     }
 
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 b49f0ac8a..d2ed248a9 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
@@ -65,7 +65,7 @@ internal class CheckInsCensorTest : BaseTest() {
             Who needs the Kwik-E-Mart in Some Street, 12345 Springfield? I doooo!
             """.trimIndent()
 
-        censor.checkLog(logLineToCensor)!!.string shouldBe """
+        censor.checkLog(logLineToCensor)!!.compile()!!.censored 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()
@@ -92,11 +92,11 @@ internal class CheckInsCensorTest : BaseTest() {
                     checkInDescription = "Moe's Tavern",
                     checkInAddress = "Near 742 Evergreen Terrace, 12345 Springfield"
                 ),
-                            /* deleted: mockCheckIn(
-                    checkInId = 2,
-                    checkInDescription = "Kwik-E-Mart",
-                    checkInAddress = "Some Street, 12345 Springfield"
-                )*/
+                /* deleted: mockCheckIn(
+        checkInId = 2,
+        checkInDescription = "Kwik-E-Mart",
+        checkInAddress = "Some Street, 12345 Springfield"
+    )*/
             )
         )
 
@@ -108,7 +108,7 @@ internal class CheckInsCensorTest : BaseTest() {
             Who needs the Kwik-E-Mart in Some Street, 12345 Springfield? I doooo!
             """.trimIndent()
 
-        censor.checkLog(logLineToCensor)!!.string shouldBe """
+        censor.checkLog(logLineToCensor)!!.compile()!!.censored 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()
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 e61a463d4..4522f2a9d 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
@@ -54,7 +54,8 @@ class CoronaTestCensorTest : BaseTest() {
     fun `censoring replaces the logline message`() = runBlockingTest {
         val instance = createInstance()
         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"
+        instance.checkLog(filterMe)!!
+            .compile()!!.censored shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier"
 
         verify { coronaTestRepository.coronaTests }
     }
@@ -83,11 +84,13 @@ class CoronaTestCensorTest : BaseTest() {
 
         val filterMe = "I'm a shy registration token: $testToken and we are extrovert $pcrIdentifier and $ratIdentifier"
 
-        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"
+        censor.checkLog(filterMe)!!
+            .compile()!!.censored 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)!!.string shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier"
+        censor.checkLog(filterMe)!!
+            .compile()!!.censored 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 75740a998..48037eeaf 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
@@ -57,7 +57,7 @@ class DiaryEncounterCensorTest : BaseTest() {
             everyone disliked that.
             """.trimIndent()
 
-        instance.checkLog(censorMe)!!.string shouldBe
+        instance.checkLog(censorMe)!!.compile()!!.censored shouldBe
             """
             On Encounter#2/Circumstances,
             two persons Encounter#3/Circumstances,
@@ -88,7 +88,7 @@ class DiaryEncounterCensorTest : BaseTest() {
             everyone disliked that.
             """.trimIndent()
 
-        instance.checkLog(censorMe)!!.string shouldBe
+        instance.checkLog(censorMe)!!.compile()!!.censored shouldBe
             """
             On Encounter#2/Circumstances,
             two persons Encounter#3/Circumstances,
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 fbc4cd1af..17610b27d 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
@@ -59,7 +59,7 @@ class DiaryLocationCensorTest : BaseTest() {
             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
+        instance.checkLog(censorMe)!!.compile()!!.censored 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,
@@ -89,7 +89,7 @@ class DiaryLocationCensorTest : BaseTest() {
             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
+        instance.checkLog(censorMe)!!.compile()!!.censored 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,
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 1d8ccac54..e94759b31 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
@@ -60,7 +60,7 @@ class DiaryPersonCensorTest : BaseTest() {
             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
+        instance.checkLog(censorMe)!!.compile()!!.censored shouldBe
             """
             Person#2/Name requested more coffee from Person#1/PhoneNumber,
             but Person#3/Name thought he had enough has had enough for today.
@@ -91,7 +91,7 @@ class DiaryPersonCensorTest : BaseTest() {
             A quick mail to luka@sap.com confirmed this.
             """.trimIndent()
 
-        instance.checkLog(censorMe)!!.string shouldBe
+        instance.checkLog(censorMe)!!.compile()!!.censored shouldBe
             """
             Person#2/Name requested more coffee from Person#1/PhoneNumber,
             but Person#3/Name thought he had enough has had enough for today.
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 3f554b853..d88f8847a 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
@@ -55,7 +55,7 @@ class DiaryVisitCensorTest : BaseTest() {
             I got my beard shaved without mask,
             only to find out the supermarket was out of toiletpaper.
             """.trimIndent()
-        instance.checkLog(censorMe)!!.string shouldBe
+        instance.checkLog(censorMe)!!.compile()!!.censored shouldBe
             """
             After having a Visit#1/Circumstances,
             I got my Visit#2/Circumstances,
@@ -84,7 +84,7 @@ class DiaryVisitCensorTest : BaseTest() {
             I got my beard shaved without mask,
             only to find out the supermarket was out of toiletpaper.
             """.trimIndent()
-        instance.checkLog(censorMe)!!.string shouldBe
+        instance.checkLog(censorMe)!!.compile()!!.censored shouldBe
             """
             After having a Visit#1/Circumstances,
             I got my Visit#2/Circumstances,
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 f7551bb70..fdf33d073 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
@@ -30,7 +30,8 @@ class PcrQrCodeCensorTest : BaseTest() {
         PcrQrCodeCensor.lastGUID = testGUID
         val instance = createInstance()
         val censored = "I'm a shy qrcode: $testGUID"
-        instance.checkLog(censored)!!.string shouldBe "I'm a shy qrcode: ########-####-####-####-########3a2f"
+        instance.checkLog(censored)!!
+            .compile()!!.censored shouldBe "I'm a shy qrcode: ########-####-####-####-########3a2f"
     }
 
     @Test
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 fa2b323d3..960614377 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
@@ -50,7 +50,7 @@ internal class RACoronaTestCensorTest : BaseTest() {
             Hello! My name is John. My friends call me Mister Doe and I was born on 2020-01-01.
             """.trimIndent()
 
-        censor.checkLog(logLineToCensor)!!.string shouldBe
+        censor.checkLog(logLineToCensor)!!.compile()!!.censored shouldBe
             """
             Hello! My name is RATest/FirstName. My friends call me Mister RATest/LastName and I was born on RATest/DateOfBirth.
             """.trimIndent()
@@ -77,7 +77,7 @@ internal class RACoronaTestCensorTest : BaseTest() {
             Hello! My name is John. My friends call me Mister Doe and I was born on 2020-01-01.
             """.trimIndent()
 
-        censor.checkLog(logLineToCensor)!!.string shouldBe
+        censor.checkLog(logLineToCensor)!!.compile()!!.censored shouldBe
             """
             Hello! My name is RATest/FirstName. My friends call me Mister RATest/LastName and I was born on RATest/DateOfBirth.
             """.trimIndent()
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 afef21806..443db598f 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
@@ -41,7 +41,8 @@ internal class RatQrCodeCensorTest {
         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)!!.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"
+        censor.checkLog(logLineToCensor)!!
+            .compile()!!.censored 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
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 cb123802a..f3d274d7c 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
@@ -51,38 +51,39 @@ internal class TraceLocationCensorTest : BaseTest() {
     }
 
     @Test
-    fun `checkLog() should return LogLine with censored trace location information from repository`() = runBlockingTest {
-        every { traceLocationRepo.allTraceLocations } returns flowOf(
-            listOf(
-                mockTraceLocation(
-                    traceLocationId = 1,
-                    traceLocationType = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_PERMANENT_FOOD_SERVICE,
-                    traceLocationDescription = "Sushi Place",
-                    traceLocationAddress = "Sushi Street 123, 12345 Fish Town"
-                ),
-                mockTraceLocation(
-                    traceLocationId = 2,
-                    traceLocationType = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT,
-                    traceLocationDescription = "Rick Astley Concert",
-                    traceLocationAddress = "Never gonna give you up street 1, 12345 RickRoll City"
+    fun `checkLog() should return LogLine with censored trace location information from repository`() =
+        runBlockingTest {
+            every { traceLocationRepo.allTraceLocations } returns flowOf(
+                listOf(
+                    mockTraceLocation(
+                        traceLocationId = 1,
+                        traceLocationType = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_PERMANENT_FOOD_SERVICE,
+                        traceLocationDescription = "Sushi Place",
+                        traceLocationAddress = "Sushi Street 123, 12345 Fish Town"
+                    ),
+                    mockTraceLocation(
+                        traceLocationId = 2,
+                        traceLocationType = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT,
+                        traceLocationDescription = "Rick Astley Concert",
+                        traceLocationAddress = "Never gonna give you up street 1, 12345 RickRoll City"
+                    )
                 )
             )
-        )
 
-        val censor = createInstance(this)
+            val censor = createInstance(this)
 
-        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()
+            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)!!.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()
-    }
+            censor.checkLog(logLineToCensor)!!.compile()!!.censored 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
     fun `censoring should still work after the user deletes his trace locations`() = runBlockingTest {
@@ -126,7 +127,7 @@ internal class TraceLocationCensorTest : BaseTest() {
             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)!!.string shouldBe
+        censor.checkLog(logLineToCensor)!!.compile()!!.censored 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.
@@ -154,7 +155,7 @@ internal class TraceLocationCensorTest : BaseTest() {
                 top secret address as the address. The type is LOCATION_TYPE_TEMPORARY_PRIVATE_EVENT. 
                 """.trimIndent()
 
-            censor.checkLog(logLineToCensor)!!.string shouldBe
+            censor.checkLog(logLineToCensor)!!.compile()!!.censored 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. 
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 9954cd867..e516437fe 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
@@ -59,7 +59,7 @@ internal class RatProfileCensorTest : BaseTest() {
             "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)!!.string shouldBe
+        censor.checkLog(logLine)!!.compile()!!.censored 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"
     }
@@ -74,11 +74,29 @@ internal class RatProfileCensorTest : BaseTest() {
             "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)!!.string shouldBe
+        censor.checkLog(logLine)!!.compile()!!.censored 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"
     }
 
+    @Test
+    fun `self overlap`() = runBlockingTest {
+        val selfOverlap = profile.copy(
+            lastName = "Berlin",
+            city = "Berlin Kreuzberg"
+        )
+        every { ratProfileSettings.profile.flow } returns flowOf(selfOverlap, null)
+
+        val censor = createInstance()
+
+        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 Berlin Kreuzberg. You can reach him by phone: 111111111 or email: email@example.com, " +
+                "NotCensored"
+
+        censor.checkLog(logLine)!!.compile()!!.censored shouldBe "Mister <censor-collision/>, NotCensored"
+    }
+
     private val formatter = DateTimeFormat.forPattern("yyyy-MM-dd")
 
     private val profile = RATProfile(
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 a5509d80b..5d2f3d05c 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
@@ -20,9 +20,9 @@ internal class CertificateQrCodeCensorTest {
             version = "1",
             nameData = VaccinationDGCV1.NameData(
                 familyName = "Kevin",
-                familyNameStandardized = "Kevin2",
+                familyNameStandardized = "KEVIN",
                 givenName = "Bob",
-                givenNameStandardized = "Bob2"
+                givenNameStandardized = "BOB"
             ),
             dob = "1969-11-16",
             vaccinationDatas = listOf(
@@ -64,16 +64,18 @@ internal class CertificateQrCodeCensorTest {
 
         val logLineToCensor = "Here comes the rawString: $testRawString of the vaccine certificate"
 
-        censor.checkLog(logLineToCensor)!!.string shouldBe "Here comes the rawString: ########-####-####-####-########C\$AH of the vaccine certificate"
+        censor.checkLog(logLineToCensor)!!
+            .compile()!!.censored shouldBe "Here comes the rawString: ########-####-####-####-########C\$AH of the vaccine certificate"
 
         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)!!.string shouldBe "Hello my name is nameData/familyName nameData/givenName, i was born at " +
-            "vaccinationCertificate/dob, i have been vaccinated with: vaccinationData/targetId " +
+        censor.checkLog(certDataToCensor)!!
+            .compile()!!.censored shouldBe "Hello my name is nameData/familyName nameData/givenName, i was born at " +
+            "vaccinationCertificate/dateOfBirth, i have been vaccinated with: vaccinationData/targetId " +
             "vaccinationData/vaccineId vaccinationData/medicalProductId" +
-            " vaccinationData/marketAuthorizationHolderId vaccinationData/dt" +
+            " vaccinationData/marketAuthorizationHolderId vaccinationData/vaccinatedAt" +
             " vaccinationData/countryOfVaccination vaccinationData/certificateIssuer" +
             " vaccinationData/uniqueCertificateIdentifier"
     }
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 24b467db1..329aed6fb 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
@@ -3,19 +3,20 @@ 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
 import de.rki.coronawarnapp.util.di.ApplicationComponent
 import io.kotest.matchers.shouldBe
 import io.kotest.matchers.shouldNotBe
+import io.kotest.matchers.string.shouldEndWith
+import io.kotest.matchers.string.shouldStartWith
 import io.mockk.MockKAnnotations
 import io.mockk.coEvery
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import io.mockk.mockkObject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.test.runBlockingTest
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
@@ -26,13 +27,13 @@ import testhelpers.logging.JUnitTree
 import timber.log.Timber
 import java.io.File
 
-@Suppress("BlockingMethodInNonBlockingContext")
+@Suppress("BlockingMethodInNonBlockingContext", "MaxLineLength")
 class DebugLoggerTest : BaseIOTest() {
 
     @MockK lateinit var application: Application
     @MockK lateinit var component: ApplicationComponent
-    @MockK lateinit var coronaTestCensor1: CoronaTestCensor
-    @MockK lateinit var coronaTestCensor2: CoronaTestCensor
+    @MockK lateinit var coronaTestCensor1: BugCensor
+    @MockK lateinit var coronaTestCensor2: BugCensor
 
     private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!)
     private val cacheDir = File(testDir, "cache")
@@ -240,7 +241,7 @@ class DebugLoggerTest : BaseIOTest() {
 
         coEvery { coronaTestCensor1.checkLog(any()) } answers {
             val msg = arg<String>(0)
-            BugCensor.CensoredString(msg).censor("says: A hot coffee", "says: A hot tea")
+            BugCensor.CensorContainer(msg).censor("says: A hot coffee", "says: A hot tea")
         }
 
         instance.start()
@@ -248,17 +249,260 @@ class DebugLoggerTest : BaseIOTest() {
         Timber.tag("Test").v(logMsg)
         advanceTimeBy(2000L)
 
+        delay(5000)
+
         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:")
+            BugCensor.CensorContainer(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!"
+        runningLog.readLines().last().substring(25) shouldBe "V/Test: Lukas <censor-collision/> is really nice!"
+
+        instance.stop()
+        advanceUntilIdle()
+    }
+
+    @Test
+    fun `censoring collision handling for multiple values in the same string`() = runBlockingTest {
+        val before =
+            """
+            RACoronaTest(
+                identifier=qrcode-RAPID_ANTIGEN-9a9a35fa1cf3261be3349fc50a37b58280634bf42487c8e4eca060c48f259eb7,
+                registeredAt=2021-05-25T10:18:05.275Z,
+                registrationToken=b0d451f9-a4ea-45ea-b634-f503458a64c9,
+                isSubmitted=false, isViewed=true, isAdvancedConsentGiven=true, isJournalEntryCreated=false,
+                isResultAvailableNotificationSent=true,
+                testResultReceivedAt=2021-05-25T10:18:41.616Z,
+                lastUpdatedAt=2021-05-25T10:18:41.152Z,
+                testResult=RAT_POSITIVE(7),
+                testedAt=2021-05-25T10:17:51.000Z,
+                firstName=Rüdiger, lastName=Müller,
+                dateOfBirth=1994-05-18, isProcessing=true,
+                lastError=null
+            )
+            updated to
+            RACoronaTest(
+                identifier=qrcode-RAPID_ANTIGEN-9a9a35fa1cf3261be3349fc50a37b58280634bf42487c8e4eca060c48f259eb7,
+                registeredAt=2021-05-25T10:18:05.275Z,
+                registrationToken=b0d451f9-a4ea-45ea-b634-f503458a64c9,
+                isSubmitted=true, isViewed=true, isAdvancedConsentGiven=true, isJournalEntryCreated=false,
+                isResultAvailableNotificationSent=true,
+                testResultReceivedAt=2021-05-25T10:18:41.616Z,
+                lastUpdatedAt=2021-05-25T10:18:41.152Z,
+                testResult=RAT_POSITIVE(7),
+                testedAt=2021-05-25T10:17:51.000Z,
+                firstName=Rüdiger, lastName=Müller,
+                dateOfBirth=1994-05-18, isProcessing=false,
+                lastError=IOException()
+            )
+            """.trimIndent()
+        val after =
+            """
+            RACoronaTest(
+                identifier=<censor-collision/>,
+                dateOfBirth=1994-05-18, isProcessing=false,
+                lastError=IOException()
+            )
+            """.trimIndent()
+
+        coEvery { coronaTestCensor1.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            BugCensor.CensorContainer(msg).censor(
+                "firstName=Rüdiger, lastName=Müller",
+                "firstName=FIRSTNAME, lastName=LASTNAME"
+            )
+        }
+        coEvery { coronaTestCensor2.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            BugCensor.CensorContainer(msg).censor(
+                "qrcode-RAPID_ANTIGEN-9a9a35fa1cf3261be3349fc50a37b58280634bf42487c8e4eca060c48f259eb7",
+                "IDENTIFIER"
+            )
+        }
+
+        val instance = createInstance(scope = this).apply {
+            init()
+            setInjectionIsReady(component)
+        }
+
+        instance.start()
+
+        Timber.tag("Test").v(before)
+        advanceTimeBy(2000L)
+
+        val rawWritten = runningLog.readText()
+        val cleanedWritten = rawWritten.substring(rawWritten.indexOf("RACoronaTest"))
+
+        cleanedWritten shouldBe after + "\n"
+
+        instance.stop()
+        advanceUntilIdle()
+    }
+
+    @Test
+    fun `censoring collision with larger than original index bounds`() = runBlockingTest {
+        val before = "shortBefore" // Without timestamp
+
+        coEvery { coronaTestCensor1.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            BugCensor.CensorContainer(msg).censor("Before", "After")
+        }
+        coEvery { coronaTestCensor2.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            var orig = BugCensor.CensorContainer(msg)
+
+            orig = orig.censor("ortBef", "thisReallyIsNotShortAnymore")
+            orig = orig.censor("Anymore", "Nevermore")
+            orig
+        }
+
+        val instance = createInstance(scope = this).apply {
+            init()
+            setInjectionIsReady(component)
+        }
+
+        instance.start()
+
+        Timber.tag("Test").v(before)
+        advanceTimeBy(2000L)
+
+        runningLog.readLines().last().substring(25) shouldBe "V/Test: sh<censor-collision/>"
+
+        instance.stop()
+        advanceUntilIdle()
+    }
+
+    // Censoring bounds need to be determined on the original string
+    @Test
+    fun `censoring collision with missmatching original and replacements`() = runBlockingTest {
+        val before = "StrawBerryCake" // Without timestamp
+
+        coEvery { coronaTestCensor1.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            BugCensor.CensorContainer(msg).censor("Berry", "Banana")
+        }
+        coEvery { coronaTestCensor2.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            var orig = BugCensor.CensorContainer(msg)
+
+            orig = orig.censor("StrawBerry", "StrawBerryBananaPie")
+            orig = orig.censor("StrawBerryBananaPie", "Apple")
+            orig
+        }
+
+        val instance = createInstance(scope = this).apply {
+            init()
+            setInjectionIsReady(component)
+        }
+
+        instance.start()
+
+        Timber.tag("Test").v(before)
+        advanceTimeBy(2000L)
+
+        runningLog.readLines().last().substring(25) shouldBe "V/Test: <censor-collision/>Cake"
+
+        instance.stop()
+        advanceUntilIdle()
+    }
+
+    @Test
+    fun `censoring collision without overlap`() = runBlockingTest {
+        val before = "StrawBerryCakeWithCream" // Without timestamp
+
+        coEvery { coronaTestCensor1.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            BugCensor.CensorContainer(msg)
+                .censor("Straw", "Strap")
+                .censor("With", "More")
+        }
+        coEvery { coronaTestCensor2.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            BugCensor.CensorContainer(msg)
+                .censor("Berry", "Barry")
+                .censor("Cream", "Sugar")
+        }
+
+        val instance = createInstance(scope = this).apply {
+            init()
+            setInjectionIsReady(component)
+        }
+
+        instance.start()
+
+        Timber.tag("Test").v(before)
+        advanceTimeBy(2000L)
+
+        runningLog.readLines()
+            .last()
+            .substring(25) shouldBe "V/Test: StrapBarryCakeMoreSugar"
+
+        instance.stop()
+        advanceUntilIdle()
+    }
+
+    @Test
+    fun `exception during single bugcensor execution`() = runBlockingTest {
+        val before = "StrawberryCake" // Without timestamp
+
+        coEvery { coronaTestCensor1.checkLog(any()) } answers {
+            null
+        }
+        coEvery { coronaTestCensor2.checkLog(any()) } answers {
+            throw IllegalArgumentException("I give up")
+        }
+
+        val instance = createInstance(scope = this).apply {
+            init()
+            setInjectionIsReady(component)
+        }
+
+        instance.start()
+
+        Timber.tag("Test").v(before)
+        advanceTimeBy(2000L)
+
+        runningLog.readLines()
+            .last()
+            .substring(25).apply {
+                this shouldStartWith "V/Test: <censor-error>Module BugCensor"
+                this shouldEndWith "lang.IllegalArgumentException: I give up</censor-error>"
+            }
+
+        instance.stop()
+        advanceUntilIdle()
+    }
+
+    @Test
+    fun `exception during multi bugcensor execution`() = runBlockingTest {
+        val before = "StrawberryCake" // Without timestamp
+
+        coEvery { coronaTestCensor1.checkLog(any()) } answers {
+            val msg = arg<String>(0)
+            BugCensor.CensorContainer(msg).censor("Cake", "Pancake")
+        }
+        coEvery { coronaTestCensor2.checkLog(any()) } answers {
+            throw IllegalArgumentException("I give up")
+        }
+
+        val instance = createInstance(scope = this).apply {
+            init()
+            setInjectionIsReady(component)
+        }
+
+        instance.start()
+
+        Timber.tag("Test").v(before)
+        advanceTimeBy(2000L)
+
+        runningLog.readLines()
+            .last()
+            .substring(25) shouldBe "V/Test: <censor-collision/>"
 
         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
index 50b05b41b..ecdbc7785 100644
--- 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
@@ -17,9 +17,14 @@ class LogLineTest : BaseTest() {
             tag = "IamATag",
             message = "Low storage check failed.",
             throwable = null
-        ).format() shouldBe """
-            1970-01-01T00:00:00.123Z E/IamATag: Low storage check failed.
-        """.trimIndent()
+        ).apply {
+            format() shouldBe """
+                Low storage check failed.
+            """.trimIndent()
+            formatFinal("abc") shouldBe """
+                1970-01-01T00:00:00.123Z E/IamATag: abc
+            """.trimIndent()
+        }
     }
 
     @Test
@@ -30,10 +35,15 @@ class LogLineTest : BaseTest() {
             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()
+        ).apply {
+            format().trimToLength(146) shouldBe """
+                Low storage check failed.
+                java.io.IOException
+                	at de.rki.coronawarnapp.bugreporting.debuglog.LogLineTest.log formatting with error(LogLineTest.kt:
+            """.trimIndent()
+            formatFinal("abc") shouldBe """
+                1970-01-01T00:00:00.123Z E/IamATag: abc
+            """.trimIndent()
+        }
     }
 }
-- 
GitLab