diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle
index a7429a68792850ee457cd7de0e2208d578015458..adaafd4b5e9a017e4b03e10911518b51c8784ccf 100644
--- a/Corona-Warn-App/build.gradle
+++ b/Corona-Warn-App/build.gradle
@@ -325,7 +325,6 @@ dependencies {
     implementation 'androidx.lifecycle:lifecycle-process:2.2.0'
     implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
     implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
-    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
     implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
 
     implementation 'androidx.annotation:annotation:1.1.0'
@@ -350,7 +349,7 @@ dependencies {
     implementation 'com.google.zxing:core:3.3.0'
 
     //ENA
-    implementation files('libs\\play-services-nearby-exposurenotification-1.7.2-eap.aar')
+    implementation files('libs/play-services-nearby-exposurenotification-1.7.2-eap.aar')
 
     // Testing
     testImplementation "androidx.arch.core:core-testing:2.1.0"
diff --git a/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml b/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml
deleted file mode 100644
index ca1b20da31c951adc75614ac532bda6681a4d23e..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:ignore="LockedOrientationActivity"
-    package="de.rki.coronawarnapp">
-
-    <application>
-
-        <provider
-            android:name="androidx.core.content.FileProvider"
-            android:authorities="${applicationId}.fileProvider"
-            android:exported="false"
-            android:grantUriPermissions="true">
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/provider_paths"/>
-        </provider>
-
-    </application>
-
-</manifest>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/deviceForTesters/res/xml/provider_paths.xml b/Corona-Warn-App/src/deviceForTesters/res/xml/provider_paths.xml
deleted file mode 100644
index b6522dfad14436380412cde73b661d34d7a1eef7..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/deviceForTesters/res/xml/provider_paths.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<paths xmlns:android="http://schemas.android.com/apk/res/android">
-    <cache-path name="share" path="share/" />
-</paths>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/AndroidManifest.xml b/Corona-Warn-App/src/main/AndroidManifest.xml
index 3c07d693f697985e7f84e98b531026292c0e7a3f..e058ffe9572563a9836bc3a95bbd28d58ef59c67 100644
--- a/Corona-Warn-App/src/main/AndroidManifest.xml
+++ b/Corona-Warn-App/src/main/AndroidManifest.xml
@@ -80,9 +80,19 @@
             android:name=".contactdiary.ui.ContactDiaryActivity"
             android:exported="false"
             android:screenOrientation="portrait"
-            android:launchMode= "singleTop"
+            android:launchMode="singleTop"
             android:theme="@style/AppTheme.ContactDiary"
-            android:windowSoftInputMode="adjustResize"/>
+            android:windowSoftInputMode="adjustResize" />
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="${applicationId}.fileProvider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/provider_paths" />
+        </provider>
 
     </application>
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
index 4fbdaee7f79dd4a70673342ce99917152ada406b..4f4b34c525793543e636095751b7bb953b9bcb2a 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
@@ -66,6 +66,8 @@ class CoronaWarnApplication : Application(), HasAndroidInjector {
         Timber.v("onCreate(): Initializing Dagger")
         AppInjector.init(this)
 
+        CWADebug.initAfterInjection(component)
+
         Timber.plant(rollingLogHistory)
 
         Timber.v("onCreate(): WorkManager setup done: $workManager")
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..99c18ab32dd265ee969d06385ec1af369b7caa22
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt
@@ -0,0 +1,22 @@
+package de.rki.coronawarnapp.bugreporting
+
+import dagger.Module
+import dagger.Provides
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor
+import de.rki.coronawarnapp.bugreporting.censors.RegistrationTokenCensor
+import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger
+import javax.inject.Singleton
+
+@Module
+class BugReportingSharedModule {
+
+    @Singleton
+    @Provides
+    fun debugLogger() = DebugLogger
+
+    @Singleton
+    @Provides
+    fun censors(
+        registrationTokenCensor: RegistrationTokenCensor
+    ): List<BugCensor> = listOf(registrationTokenCensor)
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..e9c2c69eaecdf5f9b85ad5e321db0af8cc6bcb33
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt
@@ -0,0 +1,11 @@
+package de.rki.coronawarnapp.bugreporting.censors
+
+import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
+
+interface BugCensor {
+
+    /**
+     * If there is something to censor a new log line is returned, otherwise returns null
+     */
+    fun checkLog(entry: LogLine): LogLine?
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5832e2f6f7965c1c7cc7f0427e23210201d904b6
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt
@@ -0,0 +1,23 @@
+package de.rki.coronawarnapp.bugreporting.censors
+
+import dagger.Reusable
+import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
+import de.rki.coronawarnapp.storage.LocalData
+import de.rki.coronawarnapp.util.CWADebug
+import javax.inject.Inject
+import kotlin.math.min
+
+@Reusable
+class RegistrationTokenCensor @Inject constructor() : BugCensor {
+    override fun checkLog(entry: LogLine): LogLine? {
+        val token = LocalData.registrationToken() ?: return null
+        if (!entry.message.contains(token)) return null
+
+        val replacement = if (CWADebug.isDeviceForTestersBuild) {
+            token
+        } else {
+            token.substring(0, min(4, token.length)) + "###-####-####-####-############"
+        }
+        return entry.copy(message = entry.message.replace(token, replacement))
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogTree.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogTree.kt
new file mode 100644
index 0000000000000000000000000000000000000000..68dc7f45181883d2907138e228231a228866675b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogTree.kt
@@ -0,0 +1,34 @@
+package de.rki.coronawarnapp.bugreporting.debuglog
+
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import timber.log.Timber
+
+class DebugLogTree : Timber.DebugTree() {
+
+    private val logLinesPub = MutableSharedFlow<LogLine>(
+        replay = 128,
+        extraBufferCapacity = 1024,
+        onBufferOverflow = BufferOverflow.DROP_OLDEST
+    )
+    val logLines: Flow<LogLine> = logLinesPub
+
+    init {
+        Timber.tag(TAG).d("init()")
+    }
+
+    override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
+        LogLine(
+            timestamp = System.currentTimeMillis(),
+            priority = priority,
+            tag = tag,
+            message = message,
+            throwable = t
+        ).also { logLinesPub.tryEmit(it) }
+    }
+
+    companion object {
+        private const val TAG = "DebugLogTree"
+    }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..2cdd042ea0c4f9e160ff32fb0896dd0ecc8e0653
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt
@@ -0,0 +1,166 @@
+package de.rki.coronawarnapp.bugreporting.debuglog
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.content.Context
+import android.util.Log
+import de.rki.coronawarnapp.util.di.ApplicationComponent
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.yield
+import timber.log.Timber
+import java.io.File
+
+@SuppressLint("LogNotTimber")
+@Suppress("BlockingMethodInNonBlockingContext")
+object DebugLogger : DebugLoggerBase() {
+
+    private val scope = DebugLoggerScope
+    private lateinit var context: Context
+
+    private val debugDir by lazy {
+        File(context.cacheDir, "debuglog").also {
+            if (!it.exists()) it.mkdir()
+        }
+    }
+
+    private val triggerFile by lazy { File(debugDir, "debug.trigger") }
+
+    internal val runningLog by lazy { File(debugDir, "debug.log") }
+    val sharedDirectory by lazy { File(debugDir, "shared") }
+
+    private val mutex = Mutex()
+
+    private var logJob: Job? = null
+    private var logTree: DebugLogTree? = null
+    private var isDaggerReady = false
+
+    fun init(application: Application) {
+        context = application
+
+        try {
+            if (triggerFile.exists()) {
+                Timber.tag(TAG).i("Trigger file exists, starting debug log.")
+                runBlocking { start() }
+            }
+        } catch (e: Exception) {
+            // This is called from Application.onCreate() never crash here.
+            Timber.tag(TAG).e(e, "DebugLogger init(%s) failed.", application)
+        }
+    }
+
+    /**
+     * To censor unique data, we need to actually know what to censor.
+     * So we buffer log statements until Dagger is ready
+     */
+    fun setInjectionIsReady(component: ApplicationComponent) {
+        Timber.tag(TAG).i("setInjectionIsReady()")
+        component.inject(this)
+        isDaggerReady = true
+    }
+
+    val isLogging: Boolean
+        get() = logJob?.isActive == true
+
+    suspend fun start(): Unit = mutex.withLock {
+        Timber.tag(TAG).d("start()")
+
+        if (isLogging) {
+            Timber.tag(TAG).w("Ignoring start(), already running.")
+            return@withLock
+        }
+
+        logJob?.cancel()
+        logTree?.let { Timber.uproot(it) }
+
+        DebugLogTree().apply {
+            Timber.plant(this)
+            logTree = this
+
+            if (!runningLog.exists()) {
+                runningLog.parentFile?.mkdirs()
+                if (runningLog.createNewFile()) {
+                    Timber.tag(TAG).i("Log file didn't exist and was created.")
+                }
+            }
+
+            logJob = scope.launch {
+                try {
+                    logLines.collect { rawLine ->
+                        while (!isDaggerReady) {
+                            yield()
+                        }
+                        val censoredLine = bugCensors.get().mapNotNull { it.checkLog(rawLine) }.firstOrNull()
+                        appendLogLine(censoredLine ?: rawLine)
+                    }
+                } catch (e: CancellationException) {
+                    Timber.tag(TAG).i("Logging was canceled.")
+                } catch (e: Exception) {
+                    Log.e(TAG, "Failed to call appendLogLine(...)", e)
+                }
+            }
+        }
+
+        if (!triggerFile.exists()) {
+            Timber.tag(TAG).i("Trigger file created.")
+            triggerFile.createNewFile()
+        }
+    }
+
+    suspend fun stop() = mutex.withLock {
+        Timber.tag(TAG).i("stop()")
+
+        if (triggerFile.exists() && triggerFile.delete()) {
+            Timber.tag(TAG).d("Trigger file deleted.")
+        }
+
+        logTree?.let {
+            Timber.tag(TAG).d("LogTree uprooted.")
+            Timber.uproot(it)
+        }
+        logTree = null
+
+        logJob?.let {
+            Timber.tag(TAG).d("LogJob canceled.")
+            it.cancel()
+            it.join()
+        }
+        logJob = null
+
+        if (runningLog.exists() && runningLog.delete()) {
+            Timber.tag(TAG).d("Log file was deleted.")
+        }
+
+        clearSharedFiles()
+    }
+
+    private fun appendLogLine(line: LogLine) {
+        val formattedLine = line.format(context)
+        runningLog.appendText(formattedLine, Charsets.UTF_8)
+    }
+
+    fun getLogSize(): Long = runningLog.length()
+
+    fun getShareSize(): Long = sharedDirectory.listFiles()
+        ?.fold(0L) { prev, file -> prev + file.length() }
+        ?: 0L
+
+    fun clearSharedFiles() {
+        if (!sharedDirectory.exists()) return
+
+        sharedDirectory.listFiles()?.forEach {
+            if (it.delete()) {
+                Timber.tag(TAG).d("Deleted shared file: %s", it)
+            } else {
+                Timber.tag(TAG).w("Failed to delete shared file: %s", it)
+            }
+        }
+    }
+
+    private const val TAG = "DebugLogger"
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerBase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerBase.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ee056fbd562c4e613e63aac0f8fd60173f38249b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerBase.kt
@@ -0,0 +1,12 @@
+package de.rki.coronawarnapp.bugreporting.debuglog
+
+import de.rki.coronawarnapp.bugreporting.censors.BugCensor
+import javax.inject.Inject
+
+/**
+ * Workaround for dagger injection into kotlin objects
+ */
+@Suppress("UnnecessaryAbstractClass")
+abstract class DebugLoggerBase {
+    @Inject internal lateinit var bugCensors: dagger.Lazy<List<BugCensor>>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerScope.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerScope.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4943b1f113c5861b9a3b46247acc138b24acf92e
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerScope.kt
@@ -0,0 +1,23 @@
+package de.rki.coronawarnapp.bugreporting.debuglog
+
+import de.rki.coronawarnapp.util.threads.NamedThreadFactory
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+import java.util.concurrent.Executors
+import javax.inject.Qualifier
+import javax.inject.Singleton
+import kotlin.coroutines.CoroutineContext
+
+@Singleton
+object DebugLoggerScope : CoroutineScope {
+    val dispatcher = Executors.newSingleThreadExecutor(
+        NamedThreadFactory("DebugLogger")
+    ).asCoroutineDispatcher()
+    override val coroutineContext: CoroutineContext = SupervisorJob() + dispatcher
+}
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AppScope
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
new file mode 100644
index 0000000000000000000000000000000000000000..bbc527eee8dc99d4235407073c5e787cd2145c5d
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLine.kt
@@ -0,0 +1,28 @@
+package de.rki.coronawarnapp.bugreporting.debuglog
+
+import android.content.Context
+import android.util.Log
+import org.joda.time.Instant
+
+data class LogLine(
+    val timestamp: Long,
+    val priority: Int,
+    val tag: String?,
+    val message: String,
+    val throwable: Throwable?
+) {
+
+    fun format(context: Context): String {
+        val time = Instant.ofEpochMilli(timestamp)
+        return "$time  ${priorityLabel(priority)}/$tag: $message\n"
+    }
+
+    private fun priorityLabel(priority: Int): String = when (priority) {
+        Log.ERROR -> "E"
+        Log.WARN -> "W"
+        Log.INFO -> "I"
+        Log.DEBUG -> "D"
+        Log.VERBOSE -> "V"
+        else -> priority.toString()
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..def69cfa0727f9ec0c22fbdfad50849ca48e38ac
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt
@@ -0,0 +1,62 @@
+package de.rki.coronawarnapp.bugreporting.debuglog.ui
+
+import android.os.Bundle
+import android.text.format.Formatter
+import android.view.View
+import android.widget.Toast
+import androidx.core.view.isGone
+import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.BugreportingDebuglogFragmentBinding
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.ui.popBackStack
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
+import javax.inject.Inject
+
+class DebugLogFragment : Fragment(R.layout.bugreporting_debuglog_fragment), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val vm: DebugLogViewModel by cwaViewModels { viewModelFactory }
+    private val binding: BugreportingDebuglogFragmentBinding by viewBindingLazy()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        vm.state.observe2(this) {
+            binding.apply {
+                debuglogActivityIndicator.isGone = !it.isRecording
+                debuglogStatusPrimary.text = getString(
+                    if (it.isRecording) R.string.debugging_debuglog_status_recording
+                    else R.string.debugging_debuglog_status_not_recording
+                )
+                debuglogStatusSecondary.text = getString(
+                    R.string.debugging_debuglog_status_additional_infos,
+                    Formatter.formatFileSize(context, it.currentSize)
+                )
+                toggleRecording.text = getString(
+                    if (it.isRecording) R.string.debugging_debuglog_action_stop_recording
+                    else R.string.debugging_debuglog_action_start_recording
+                )
+                shareRecording.isEnabled = it.currentSize > 0L && !it.sharingInProgress
+                toggleRecording.isEnabled = !it.sharingInProgress
+            }
+        }
+
+        vm.errorEvent.observe2(this) {
+            Toast.makeText(requireContext(), it.toString(), Toast.LENGTH_LONG).show()
+        }
+
+        vm.shareEvent.observe2(this) {
+            startActivity(it.get(requireActivity()))
+        }
+
+        binding.apply {
+            toggleRecording.setOnClickListener { vm.toggleRecording() }
+            shareRecording.setOnClickListener { vm.shareRecording() }
+            toolbar.setNavigationOnClickListener { popBackStack() }
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragmentModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..11725335abd04709340bc9f82ebedba55b01d473
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragmentModule.kt
@@ -0,0 +1,20 @@
+package de.rki.coronawarnapp.bugreporting.debuglog.ui
+
+import dagger.Binds
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
+
+@Module
+abstract class DebugLogFragmentModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(DebugLogViewModel::class)
+    abstract fun onboardingNotificationsVM(factory: DebugLogViewModel.Factory): CWAViewModelFactory<out CWAViewModel>
+
+    @ContributesAndroidInjector
+    abstract fun debuglogFragment(): DebugLogFragment
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..027a0793c78a9ddf22a4b8fce44172235f6daa65
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt
@@ -0,0 +1,115 @@
+package de.rki.coronawarnapp.bugreporting.debuglog.ui
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.asLiveData
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger
+import de.rki.coronawarnapp.nearby.ENFClient
+import de.rki.coronawarnapp.util.CWADebug
+import de.rki.coronawarnapp.util.TimeStamper
+import de.rki.coronawarnapp.util.compression.Zipper
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.sharing.FileSharing
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
+import org.joda.time.format.DateTimeFormat
+import timber.log.Timber
+import java.io.File
+
+class DebugLogViewModel @AssistedInject constructor(
+    private val debugLogger: DebugLogger,
+    dispatcherProvider: DispatcherProvider,
+    private val timeStamper: TimeStamper,
+    private val fileSharing: FileSharing,
+    private val enfClient: ENFClient
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+    private val ticker = flow {
+        while (true) {
+            emit(Unit)
+            delay(500)
+        }
+    }
+    private val manualTick = MutableStateFlow(Unit)
+    private val sharingInProgress = MutableStateFlow(false)
+    val state: LiveData<State> = combine(ticker, manualTick, sharingInProgress) { _, _, sharingInProgress ->
+        State(
+            isRecording = debugLogger.isLogging,
+            currentSize = debugLogger.getLogSize() + debugLogger.getShareSize(),
+            sharingInProgress = sharingInProgress
+        )
+    }.asLiveData(context = dispatcherProvider.Default)
+
+    val errorEvent = SingleLiveEvent<Throwable>()
+    val shareEvent = SingleLiveEvent<FileSharing.ShareIntentProvider>()
+
+    fun toggleRecording() = launch {
+        try {
+            if (debugLogger.isLogging) {
+                debugLogger.stop()
+            } else {
+                debugLogger.start()
+                printExtendedLogInfos()
+            }
+        } catch (e: Exception) {
+            errorEvent.postValue(e)
+        } finally {
+            manualTick.value = Unit
+        }
+    }
+
+    private suspend fun printExtendedLogInfos() {
+        CWADebug.logDeviceInfos()
+        try {
+            val enfVersion = enfClient.getENFClientVersion()
+            Timber.tag("ENFClient").i("ENF Version: %d", enfVersion)
+        } catch (e: Exception) {
+            Timber.tag("ENFClient").e(e, "Failed to get ENF version for debug log.")
+        }
+    }
+
+    fun shareRecording() {
+        sharingInProgress.value = true
+        launch {
+            try {
+                debugLogger.clearSharedFiles()
+
+                val now = timeStamper.nowUTC
+                val formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")
+                val formattedFileName = "CWA Log ${now.toString(formatter)}"
+                val zipFile = File(debugLogger.sharedDirectory, "$formattedFileName.zip")
+
+                Zipper(zipFile).zip(
+                    listOf(Zipper.Entry(name = "$formattedFileName.txt", path = debugLogger.runningLog))
+                )
+
+                val intentProvider = fileSharing.getIntentProvider(
+                    path = zipFile,
+                    title = zipFile.name,
+                    chooserTitle = R.string.debugging_debuglog_sharing_dialog_title
+                )
+
+                shareEvent.postValue(intentProvider)
+            } catch (e: Exception) {
+                Timber.e(e, "Sharing debug log failed.")
+                errorEvent.postValue(e)
+            } finally {
+                sharingInProgress.value = false
+            }
+        }
+    }
+
+    data class State(
+        val isRecording: Boolean,
+        val sharingInProgress: Boolean = false,
+        val currentSize: Long = 0
+    )
+
+    @AssistedInject.Factory
+    interface Factory : SimpleCWAViewModelFactory<DebugLogViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt
index caac006b32562d2d7ccffa0881ad81d6eb3eec9a..198e37eaeb01c901af3b5ec9372c52a57ec48dad 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt
@@ -5,15 +5,16 @@ import android.os.Bundle
 import android.view.View
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
+import androidx.core.view.isGone
 import androidx.fragment.app.Fragment
-import androidx.navigation.fragment.findNavController
 import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentInformationBinding
-import de.rki.coronawarnapp.ui.doNavigate
 import de.rki.coronawarnapp.ui.main.MainActivity
+import de.rki.coronawarnapp.util.CWADebug
 import de.rki.coronawarnapp.util.ExternalActionHelper
 import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.ui.doNavigate
 import de.rki.coronawarnapp.util.ui.observe2
 import de.rki.coronawarnapp.util.ui.setGone
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
@@ -55,6 +56,9 @@ class InformationFragment : Fragment(R.layout.fragment_information), AutoInject
 
         setButtonOnClickListener()
         setAccessibilityDelegate()
+
+        // TODO Hidden until further clarification regarding release schedule is available
+        binding.informationDebuglog.mainRow.isGone = !CWADebug.isDeviceForTestersBuild
     }
 
     override fun onResume() {
@@ -76,22 +80,22 @@ class InformationFragment : Fragment(R.layout.fragment_information), AutoInject
 
     private fun setButtonOnClickListener() {
         binding.informationAbout.mainRow.setOnClickListener {
-            findNavController().doNavigate(
+            doNavigate(
                 InformationFragmentDirections.actionInformationFragmentToInformationAboutFragment()
             )
         }
         binding.informationPrivacy.mainRow.setOnClickListener {
-            findNavController().doNavigate(
+            doNavigate(
                 InformationFragmentDirections.actionInformationFragmentToInformationPrivacyFragment()
             )
         }
         binding.informationTerms.mainRow.setOnClickListener {
-            findNavController().doNavigate(
+            doNavigate(
                 InformationFragmentDirections.actionInformationFragmentToInformationTermsFragment()
             )
         }
         binding.informationContact.mainRow.setOnClickListener {
-            findNavController().doNavigate(
+            doNavigate(
                 InformationFragmentDirections.actionInformationFragmentToInformationContactFragment()
             )
         }
@@ -99,15 +103,20 @@ class InformationFragment : Fragment(R.layout.fragment_information), AutoInject
             ExternalActionHelper.openUrl(this, requireContext().getString(R.string.main_about_link))
         }
         binding.informationLegal.mainRow.setOnClickListener {
-            findNavController().doNavigate(
+            doNavigate(
                 InformationFragmentDirections.actionInformationFragmentToInformationLegalFragment()
             )
         }
         binding.informationTechnical.mainRow.setOnClickListener {
-            findNavController().doNavigate(
+            doNavigate(
                 InformationFragmentDirections.actionInformationFragmentToInformationTechnicalFragment()
             )
         }
+        binding.informationDebuglog.mainRow.setOnClickListener {
+            doNavigate(
+                InformationFragmentDirections.actionInformationFragmentToDebuglogFragment()
+            )
+        }
         binding.informationHeader.headerButtonBack.buttonIcon.setOnClickListener {
             (activity as MainActivity).goBack()
         }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragmentModule.kt
index 7b7473491c6c067a89555f75535491ffbdd81b27..e52ddb99205c428db477710548307ba7caf0b4f2 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragmentModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragmentModule.kt
@@ -4,11 +4,12 @@ import dagger.Binds
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
 import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.bugreporting.debuglog.ui.DebugLogFragmentModule
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
 
-@Module
+@Module(includes = [DebugLogFragmentModule::class])
 abstract class InformationFragmentModule {
     @Binds
     @IntoMap
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt
index 9b474acf26fb9d4a1d35d9a470f804febf33e92f..4b959e3f8f1c67e3e60cc1ba19b0b47f610daa50 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt
@@ -3,7 +3,9 @@ package de.rki.coronawarnapp.util
 import android.app.Application
 import android.os.Build
 import de.rki.coronawarnapp.BuildConfig
+import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger
 import de.rki.coronawarnapp.util.debug.FileLogger
+import de.rki.coronawarnapp.util.di.ApplicationComponent
 import timber.log.Timber
 
 object CWADebug {
@@ -19,9 +21,13 @@ object CWADebug {
             fileLogger = FileLogger(application)
         }
 
-        Timber.i("CWA version: %s (%s)", BuildConfig.VERSION_CODE, BuildConfig.GIT_COMMIT_SHORT_HASH)
-        Timber.i("CWA flavor: %s (%s)", BuildConfig.FLAVOR, BuildConfig.BUILD_TYPE)
-        Timber.i("Build.FINGERPRINT: %s", Build.FINGERPRINT)
+        DebugLogger.init(application)
+
+        logDeviceInfos()
+    }
+
+    fun initAfterInjection(component: ApplicationComponent) {
+        DebugLogger.setInjectionIsReady(component)
     }
 
     val isDebugBuildOrMode: Boolean
@@ -45,4 +51,10 @@ object CWADebug {
             false
         }
     }
+
+    fun logDeviceInfos() {
+        Timber.i("CWA version: %s (%s)", BuildConfig.VERSION_CODE, BuildConfig.GIT_COMMIT_SHORT_HASH)
+        Timber.i("CWA flavor: %s (%s)", BuildConfig.FLAVOR, BuildConfig.BUILD_TYPE)
+        Timber.i("Build.FINGERPRINT: %s", Build.FINGERPRINT)
+    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/compression/Zipper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/compression/Zipper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2c7924c2b5e2a462303ef3f74b32452e090e466a
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/compression/Zipper.kt
@@ -0,0 +1,44 @@
+package de.rki.coronawarnapp.util.compression
+
+import timber.log.Timber
+import java.io.File
+import java.io.IOException
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+
+class Zipper(private val zipPath: File) {
+
+    fun zip(toZip: List<Entry>) {
+        if (zipPath.exists()) throw IOException("$zipPath already exists")
+
+        Timber.tag(TAG).d("Creating ZIP file: %s", zipPath)
+        zipPath.parentFile?.mkdirs()
+        zipPath.createNewFile()
+
+        if (!zipPath.exists()) throw IOException("Could not create $zipPath")
+
+        ZipOutputStream(zipPath.outputStream().buffered()).use { output ->
+            for (i in toZip.indices) {
+                Timber.tag(TAG).v("Compressing ${toZip[i]} into $zipPath")
+
+                val item = toZip[i]
+                Timber.tag(TAG).v("Reading %s (size=%d)", item.path, item.path.length())
+                item.path.inputStream().buffered().use { input ->
+                    output.putNextEntry(ZipEntry(item.name))
+                    input.copyTo(output)
+                }
+            }
+        }
+
+        Timber.tag(TAG).i("ZipFile finished: %s", zipPath)
+    }
+
+    data class Entry(
+        val path: File,
+        val name: String = path.name
+    )
+
+    companion object {
+        private const val TAG = "ZipFile"
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
index ba3270685ab4ef1d76c153c40f842d7b547cc0ed..793ba8478adc68b9bce2772e24dab5a12225f86e 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
@@ -9,6 +9,8 @@ import de.rki.coronawarnapp.appconfig.AppConfigModule
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.bugreporting.BugReporter
 import de.rki.coronawarnapp.bugreporting.BugReportingModule
+import de.rki.coronawarnapp.bugreporting.BugReportingSharedModule
+import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger
 import de.rki.coronawarnapp.contactdiary.ContactDiaryRootModule
 import de.rki.coronawarnapp.diagnosiskeys.DiagnosisKeysModule
 import de.rki.coronawarnapp.diagnosiskeys.DownloadDiagnosisKeysTaskModule
@@ -64,6 +66,7 @@ import javax.inject.Singleton
         TaskModule::class,
         DeviceForTestersModule::class,
         BugReportingModule::class,
+        BugReportingSharedModule::class,
         SerializationModule::class,
         WorkerBinder::class,
         ContactDiaryRootModule::class
@@ -88,6 +91,8 @@ interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> {
 
     val bugReporter: BugReporter
 
+    fun inject(logger: DebugLogger)
+
     @Component.Factory
     interface Factory {
         fun create(@BindsInstance app: CoronaWarnApplication): ApplicationComponent
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/sharing/FileSharing.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/sharing/FileSharing.kt
new file mode 100644
index 0000000000000000000000000000000000000000..59dff05d68a47b0d753c7edfafbc7c5f01a9e156
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/sharing/FileSharing.kt
@@ -0,0 +1,66 @@
+package de.rki.coronawarnapp.util.sharing
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.annotation.StringRes
+import androidx.core.app.ShareCompat
+import androidx.core.content.FileProvider
+import dagger.Reusable
+import de.rki.coronawarnapp.BuildConfig
+import de.rki.coronawarnapp.util.di.AppContext
+import timber.log.Timber
+import java.io.File
+import javax.inject.Inject
+
+@Reusable
+class FileSharing @Inject constructor(
+    @AppContext private val context: Context
+) {
+
+    private fun getFileUri(path: File): Uri = FileProvider.getUriForFile(
+        context,
+        AUTHORITY,
+        path
+    )
+
+    fun getIntentProvider(
+        path: File,
+        title: String,
+        @StringRes chooserTitle: Int? = null
+    ): ShareIntentProvider = object : ShareIntentProvider {
+        override fun get(activity: Activity): Intent {
+            val builder = ShareCompat.IntentBuilder.from(activity).apply {
+                setType(determineMimeType(path))
+                setStream(getFileUri(path))
+                setSubject(title)
+                chooserTitle?.let { setChooserTitle(it) }
+            }
+
+            val intent = if (chooserTitle != null) {
+                builder.createChooserIntent()
+            } else {
+                builder.intent
+            }
+            return intent.apply {
+                addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                Timber.tag(TAG).d("Intent created %s", this)
+            }
+        }
+    }
+
+    interface ShareIntentProvider {
+        fun get(activity: Activity): Intent
+    }
+
+    private fun determineMimeType(path: File): String = when {
+        path.name.endsWith(".zip") -> "application/zip"
+        else -> throw UnsupportedOperationException("Unsupported MIME type: $path")
+    }
+
+    companion object {
+        private const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.fileProvider"
+        private const val TAG = "FileSharing"
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/threads/NamedThreadFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/threads/NamedThreadFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c25d83b9528f348fcf4b97d55464b6fbb1e1551b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/threads/NamedThreadFactory.kt
@@ -0,0 +1,17 @@
+package de.rki.coronawarnapp.util.threads
+
+import java.util.Locale
+import java.util.concurrent.ThreadFactory
+import java.util.concurrent.atomic.AtomicLong
+
+class NamedThreadFactory(private val threadPrefix: String) : ThreadFactory {
+    private val threadIndex = AtomicLong(1)
+
+    override fun newThread(runnable: Runnable): Thread = Thread(runnable).apply {
+        name = if (threadPrefix.contains("%d")) {
+            String.format(Locale.ROOT, threadPrefix, threadIndex.getAndIncrement())
+        } else {
+            "$threadPrefix-${threadIndex.getAndIncrement()}"
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml b/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c33bf0eb51c4e7592e3811508f95817d162f132f
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <Toolbar
+        android:id="@+id/toolbar"
+        style="@style/CWAToolbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:navigationIcon="@drawable/ic_back"
+        android:title="@string/debugging_debuglog_title" />
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <TextView
+                android:id="@+id/explanation"
+                style="@style/body1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/spacing_normal"
+                android:text="@string/debugging_debuglog_intro_explanation" />
+
+            <TextView
+                android:id="@+id/sensitive_information"
+                style="@style/body1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@color/cwaGrayHighlight"
+                android:padding="@dimen/spacing_normal"
+                android:text="@string/debugging_debuglog_intro_warning"
+                android:textColor="@color/colorStableLight" />
+
+            <androidx.constraintlayout.widget.ConstraintLayout
+                style="@style/cardTracing"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/spacing_small"
+                android:layout_marginTop="@dimen/spacing_normal"
+                android:layout_marginEnd="@dimen/spacing_small"
+                android:layout_marginBottom="@dimen/spacing_tiny">
+
+                <ProgressBar
+                    android:id="@+id/debuglog_activity_indicator"
+                    android:layout_width="36dp"
+                    android:layout_height="36dp"
+                    android:visibility="gone"
+                    app:layout_constraintBottom_toBottomOf="@+id/debuglog_status_secondary"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="@+id/debuglog_status_primary"
+                    tools:visibility="visible" />
+
+                <TextView
+                    android:id="@+id/debuglog_status_primary"
+                    style="@style/body1"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="16dp"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toEndOf="@+id/debuglog_activity_indicator"
+                    app:layout_constraintTop_toTopOf="parent"
+                    app:layout_goneMarginStart="0dp"
+                    tools:text="@string/debugging_debuglog_status_not_recording" />
+
+                <TextView
+                    android:id="@+id/debuglog_status_secondary"
+                    style="@style/TextAppearance.AppCompat.Caption"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="16dp"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toEndOf="@+id/debuglog_activity_indicator"
+                    app:layout_constraintTop_toBottomOf="@id/debuglog_status_primary"
+                    app:layout_goneMarginStart="0dp"
+                    tools:text="@string/debugging_debuglog_status_additional_infos" />
+
+                <Button
+                    android:id="@+id/share_recording"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="16dp"
+                    android:layout_marginEnd="8dp"
+                    android:enabled="false"
+                    android:text="@string/debugging_debuglog_action_share_log"
+                    app:layout_constraintEnd_toStartOf="@+id/toggle_recording"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@id/debuglog_status_secondary" />
+                <Button
+                    android:id="@+id/toggle_recording"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="8dp"
+                    android:layout_marginTop="16dp"
+                    android:text="@string/debugging_debuglog_action_start_recording"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toEndOf="@id/share_recording"
+                    app:layout_constraintTop_toBottomOf="@id/debuglog_status_secondary" />
+
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+        </LinearLayout>
+
+    </ScrollView>
+</LinearLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_information.xml b/Corona-Warn-App/src/main/res/layout/fragment_information.xml
index 09abab386fbd8ad10175f5c88d9e08f1cc730ec8..783c68a5e79461e9a035f3c7ed9ebcd53272e6ca 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_information.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_information.xml
@@ -106,29 +106,39 @@
                     app:layout_constraintTop_toBottomOf="@+id/information_contact"
                     app:subtitle="@{@string/information_legal_title}" />
 
+                <include
+                    android:id="@+id/information_debuglog"
+                    layout="@layout/include_row"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/information_contact"
+                    app:subtitle="@{@string/debugging_debuglog_title}" />
+
                 <TextView
                     android:id="@+id/information_version"
                     style="@style/body2Medium"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
+                    android:layout_marginStart="@dimen/guideline_body_title_padding"
                     android:layout_marginTop="@dimen/spacing_small"
                     android:focusable="true"
-                    tools:text="v1.8.0-RC1"
-                    android:layout_marginStart="@dimen/guideline_body_title_padding" />
+                    tools:text="v1.8.0-RC1" />
 
                 <TextView
                     android:id="@+id/information_enf_version"
                     style="@style/body2Medium"
-                    android:visibility="gone"
-                    tools:visibility="visible"
                     android:layout_width="match_parent"
-                    android:paddingTop="@dimen/spacing_tiny"
-                    android:paddingBottom="@dimen/spacing_tiny"
                     android:layout_height="wrap_content"
-                    android:focusable="true"
+                    android:layout_marginStart="@dimen/guideline_body_title_padding"
                     android:background="?selectableItemBackground"
+                    android:focusable="true"
+                    android:paddingTop="@dimen/spacing_tiny"
+                    android:paddingBottom="@dimen/spacing_tiny"
+                    android:visibility="gone"
                     tools:text="16000000"
-                    android:layout_marginStart="@dimen/guideline_body_title_padding" />
+                    tools:visibility="visible" />
 
 
             </LinearLayout>
diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
index 92042f0b6bc774d873839f2ca6d2ad81a1621add..63f88702b91bef501c8116e74f14b0d7b16de639 100644
--- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
+++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
@@ -168,6 +168,9 @@
         <action
             android:id="@+id/action_informationFragment_to_informationTechnicalFragment"
             app:destination="@id/informationTechnicalFragment" />
+        <action
+            android:id="@+id/action_informationFragment_to_debuglogFragment"
+            app:destination="@id/debuglogFragment" />
     </fragment>
 
     <fragment
@@ -479,4 +482,8 @@
         android:name="de.rki.coronawarnapp.ui.submission.testresult.invalid.SubmissionTestResultInvalidFragment"
         android:label="SubmissionTestResultInvalidFragment"
         tools:layout="@layout/fragment_submission_test_result_invalid" />
+    <fragment
+        android:id="@+id/debuglogFragment"
+        android:name="de.rki.coronawarnapp.bugreporting.debuglog.ui.DebugLogFragment"
+        android:label="DebuglogFragment" />
 </navigation>
diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml
index 8d4bccf40d294e233d1fb8777682df98444e21ab..fcd6ecfe4212e351323926f1b26693efab05758f 100644
--- a/Corona-Warn-App/src/main/res/values-de/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/strings.xml
@@ -733,6 +733,27 @@
     <!-- XACT: describes illustration -->
     <string name="information_legal_illustration_description">"Eine Hand hält ein Smartphone mit viel Text, daneben ist ein Paragraphenzeichen als Symbol für das Impressum."</string>
 
+    <!-- XHED: Headline for debug log screen -->
+    <string name="debugging_debuglog_title">"Fehlerbericht"</string>
+    <!-- YTXT: Description for the debug option to record log files -->
+    <string name="debugging_debuglog_intro_explanation">"Durch diese Option wird das App-Verhalten in einer Textdatei protokolliert. Ist die Option aktiviert bevor ein Fehler auftritt, können Sie diesen Bericht zur Verfügung stellen, um die Entwickler bei der Problemlösung zu unterstützen.\nEine dauerhafte Aktivierung dieser Option führt zu hohem Speicherbedarf. Der Fehlerbericht wird durch Deaktivieren der Option gelöscht."</string>
+    <!-- YTXT: Warning regarding downsides of recording a log file -->
+    <string name="debugging_debuglog_intro_warning">"Bitte beachten Sie, dass in Fehlerberichten Informationen über Risikoberechnung und Testergebnis enthalten sein können. Aus diesem Grund sollten Sie Fehlerberichte nicht öffentlich teilen."</string>
+    <!-- XBUT: Button text to start the log recording -->
+    <string name="debugging_debuglog_action_start_recording">"Starten"</string>
+    <!-- XBUT: Button text to stop the log recording -->
+    <string name="debugging_debuglog_action_stop_recording">"Löschen"</string>
+    <!-- XBUT: Button text to share the log recording -->
+    <string name="debugging_debuglog_action_share_log">"Teilen"</string>
+    <!-- YTXT: Status text if a debug log is being recorded -->
+    <string name="debugging_debuglog_status_recording">"Aufzeichnung läuft"</string>
+    <!-- YTXT: Status text if a debug log is not being recorded -->
+    <string name="debugging_debuglog_status_not_recording">"Inaktiv"</string>
+    <!-- YTXT: Describtion for current logging status text if a debug log is not being recorded -->
+    <string name="debugging_debuglog_status_additional_infos">"Derzeitige Größe: %1$s (unkomprimiert)"</string>
+    <!-- XHED: Title for native sharing dialog -->
+    <string name="debugging_debuglog_sharing_dialog_title">"CWA Fehlerbericht teilen"</string>
+
     <!-- ####################################
               Interoperability
     ###################################### -->
diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml
index 4a26af31fa2b8776cbb3cbd09a10d52c196f40f0..85b8f9eefa83e8d7fa952d1ec4ac41cd7113ba44 100644
--- a/Corona-Warn-App/src/main/res/values/strings.xml
+++ b/Corona-Warn-App/src/main/res/values/strings.xml
@@ -739,6 +739,28 @@
     <!-- XACT: describes illustration -->
     <string name="information_legal_illustration_description">"A hand holds a smartphone displaying a large body of text on the screen. Next to the text is a section symbol representing the imprint."</string>
 
+
+    <!-- XHED: Headline for debug log screen -->
+    <string name="debugging_debuglog_title" />
+    <!-- YTXT: Description for the debug option to record log files -->
+    <string name="debugging_debuglog_intro_explanation"></string>
+    <!-- YTXT: Warning regarding downsides of recording a log file -->
+    <string name="debugging_debuglog_intro_warning" />
+    <!-- XBUT: Button text to start the log recording -->
+    <string name="debugging_debuglog_action_start_recording" />
+    <!-- XBUT: Button text to stop the log recording -->
+    <string name="debugging_debuglog_action_stop_recording" />
+    <!-- XBUT: Button text to share the log recording -->
+    <string name="debugging_debuglog_action_share_log" />
+    <!-- YTXT: Status text if a debug log is being recorded -->
+    <string name="debugging_debuglog_status_recording" />
+    <!-- YTXT: Status text if a debug log is not being recorded -->
+    <string name="debugging_debuglog_status_not_recording" />
+    <!-- YTXT: Describtion for current logging status text if a debug log is not being recorded -->
+    <string name="debugging_debuglog_status_additional_infos">"%1$s"</string>
+    <!-- XHED: Title for native sharing dialog -->
+    <string name="debugging_debuglog_sharing_dialog_title" />
+
     <!-- ####################################
               Interoperability
     ###################################### -->
diff --git a/Corona-Warn-App/src/main/res/xml/provider_paths.xml b/Corona-Warn-App/src/main/res/xml/provider_paths.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cc1e648e7f702573ec5da2f64b42a4ed4a409fb0
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths>
+    <cache-path
+        name="share"
+        path="share/" />
+    <cache-path
+        name="shared_logs"
+        path="debuglog/shared/" />
+</paths>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8c13580efb27805aea69afab697fdb953436cff1
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt
@@ -0,0 +1,86 @@
+package de.rki.coronawarnapp.bugreporting.censors
+
+import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
+import de.rki.coronawarnapp.storage.LocalData
+import de.rki.coronawarnapp.util.CWADebug
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.clearAllMocks
+import io.mockk.every
+import io.mockk.mockkObject
+import io.mockk.verify
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class RegistrationTokenCensorTest : BaseTest() {
+
+    private val testToken = "63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f"
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        mockkObject(CWADebug)
+        every { CWADebug.isDeviceForTestersBuild } returns false
+
+        mockkObject(LocalData)
+        every { LocalData.registrationToken() } returns testToken
+    }
+
+    @AfterEach
+    fun teardown() {
+        clearAllMocks()
+    }
+
+    private fun createInstance() = RegistrationTokenCensor()
+
+    @Test
+    fun `censoring replaces the logline message`() {
+        val instance = createInstance()
+        val filterMe = LogLine(
+            timestamp = 1,
+            priority = 3,
+            message = "I'm a shy registration token: $testToken",
+            tag = "I'm a tag",
+            throwable = null
+        )
+        instance.checkLog(filterMe) shouldBe filterMe.copy(
+            message = "I'm a shy registration token: 63b4###-####-####-####-############"
+        )
+
+        verify { LocalData.registrationToken() }
+    }
+
+    @Test
+    fun `censoring returns null if thereis no match`() {
+        val instance = createInstance()
+        val filterMeNot = LogLine(
+            timestamp = 1,
+            priority = 3,
+            message = "I'm not a registration token ;)",
+            tag = "I'm a tag",
+            throwable = null
+        )
+        instance.checkLog(filterMeNot) shouldBe null
+    }
+
+    @Test
+    fun `token is not censored on tester builds`() {
+        every { CWADebug.isDeviceForTestersBuild } returns true
+        val instance = createInstance()
+        val filterMe = LogLine(
+            timestamp = 1,
+            priority = 3,
+            message = "I'm a shy registration token: $testToken",
+            tag = "I'm a tag",
+            throwable = null
+        )
+        instance.checkLog(filterMe) shouldBe filterMe.copy(
+            message = "I'm a shy registration token: 63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f"
+        )
+
+        verify { LocalData.registrationToken() }
+    }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..99678b82732651514deb19468f0a5eb673214a3f
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt
@@ -0,0 +1,170 @@
+package de.rki.coronawarnapp.bugreporting.debuglog
+
+import android.app.Application
+import dagger.Lazy
+import de.rki.coronawarnapp.bugreporting.censors.RegistrationTokenCensor
+import de.rki.coronawarnapp.util.di.ApplicationComponent
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.shouldNotBe
+import io.mockk.MockKAnnotations
+import io.mockk.clearAllMocks
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import kotlinx.coroutines.runBlocking
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseIOTest
+import testhelpers.logging.JUnitTree
+import timber.log.Timber
+import java.io.File
+import kotlin.random.Random
+
+class DebugLoggerTest : BaseIOTest() {
+
+    @MockK lateinit var application: Application
+    @MockK lateinit var component: ApplicationComponent
+    @MockK lateinit var registrationTokenCensor: RegistrationTokenCensor
+
+    private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!)
+    private val cacheDir = File(testDir, "cache")
+    private val debugLogDir = File(cacheDir, "debuglog")
+    private val sharedDir = File(debugLogDir, "shared")
+    private val runningLog = File(debugLogDir, "debug.log")
+    private val triggerFile = File(debugLogDir, "debug.trigger")
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+        testDir.mkdirs()
+        testDir.exists() shouldBe true
+
+        every { application.cacheDir } returns cacheDir
+        every { component.inject(any<DebugLogger>()) } answers {
+            val logger = arg<DebugLogger>(0)
+            logger.bugCensors = Lazy { listOf(registrationTokenCensor) }
+        }
+    }
+
+    @AfterEach
+    fun teardown() {
+        runBlocking { DebugLogger.stop() }
+        testDir.deleteRecursively()
+        clearAllMocks()
+        Timber.uprootAll()
+    }
+
+    private fun createInstance() = DebugLogger
+
+    @Test
+    fun `init does nothing if there is no trigger file`() {
+        createInstance().apply {
+            init(application)
+            isLogging shouldBe false
+        }
+        runningLog.exists() shouldBe false
+        Timber.forest().apply {
+            size shouldBe 1
+            (first() is JUnitTree) shouldBe true
+        }
+    }
+
+    @Test
+    fun `init calls start if there is a trigger file`() {
+        triggerFile.parentFile?.mkdirs()
+        triggerFile.createNewFile()
+        createInstance().apply {
+            init(application)
+            isLogging shouldBe true
+        }
+        runningLog.exists() shouldBe true
+    }
+
+    @Test
+    fun `start plants a tree and starts a logging coroutine`() {
+        val instance = createInstance().apply {
+            init(application)
+            isLogging shouldBe false
+        }
+
+        Timber.forest().none { it is DebugLogTree } shouldBe true
+
+        runBlocking {
+            instance.start()
+            Timber.forest().single { it is DebugLogTree } shouldNotBe null
+        }
+    }
+
+    @Test
+    fun `multiple start have no effect`() {
+        val instance = createInstance().apply {
+            init(application)
+            isLogging shouldBe false
+        }
+
+        Timber.forest().none { it is DebugLogTree } shouldBe true
+
+        File(sharedDir, "1").apply {
+            parentFile?.mkdirs()
+            appendBytes(Random.nextBytes(10))
+        }
+
+        runBlocking {
+            instance.start()
+            instance.start()
+            instance.start()
+
+            Timber.forest().single { it is DebugLogTree } shouldNotBe null
+            sharedDir.listFiles()!!.size shouldBe 1
+
+            instance.stop()
+            instance.stop()
+
+            Timber.forest().none { it is DebugLogTree } shouldBe true
+            DebugLogger.isLogging shouldBe false
+            sharedDir.listFiles()!!.size shouldBe 0
+        }
+    }
+
+    @Test
+    fun `stop cancels the coroutine and uproots the tree and deletes any logs`() {
+        val instance = createInstance().apply {
+            init(application)
+            isLogging shouldBe false
+        }
+
+        Timber.forest().none { it is DebugLogTree } shouldBe true
+
+        runBlocking {
+            instance.start()
+            Timber.forest().single { it is DebugLogTree } shouldNotBe null
+
+            instance.stop()
+            Timber.forest().none { it is DebugLogTree } shouldBe true
+            DebugLogger.isLogging shouldBe false
+
+            runningLog.exists() shouldBe false
+        }
+    }
+
+    @Test
+    fun `log size returns current logfile size`() {
+        runningLog.parentFile?.mkdirs()
+        runningLog.appendBytes(Random.nextBytes(22))
+        createInstance().apply {
+            init(application)
+            getLogSize() shouldBe 22
+        }
+    }
+
+    @Test
+    fun `shared size aggregates shared folder size`() {
+        sharedDir.mkdirs()
+        File(sharedDir, "1").apply { appendBytes(Random.nextBytes(10)) }
+        File(sharedDir, "2").apply { appendBytes(Random.nextBytes(15)) }
+        createInstance().apply {
+            init(application)
+            getShareSize() shouldBe 25
+        }
+    }
+}