Skip to content

Commit

Permalink
logfile timestamp, add log dump metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
crc-32 committed Jun 12, 2024
1 parent 4b6f552 commit eee9d18
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package io.rebble.cobble.bluetooth

sealed class ConnectionState {
object Disconnected : ConnectionState()
class WaitingForBluetoothToEnable(val watch: PebbleDevice?) : ConnectionState()
class WaitingForReconnect(val watch: PebbleDevice?) : ConnectionState()
class Connecting(val watch: PebbleDevice?) : ConnectionState()
class Negotiating(val watch: PebbleDevice?) : ConnectionState()
class Connected(val watch: PebbleDevice) : ConnectionState()
class RecoveryMode(val watch: PebbleDevice) : ConnectionState()
data class WaitingForBluetoothToEnable(val watch: PebbleDevice?) : ConnectionState()
data class WaitingForReconnect(val watch: PebbleDevice?) : ConnectionState()
data class Connecting(val watch: PebbleDevice?) : ConnectionState()
data class Negotiating(val watch: PebbleDevice?) : ConnectionState()
data class Connected(val watch: PebbleDevice) : ConnectionState()
data class RecoveryMode(val watch: PebbleDevice) : ConnectionState()
}

val ConnectionState.watchOrNull: PebbleDevice?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class DebugFlutterBridge @Inject constructor(
bridgeLifecycleController.setupControl(Pigeons.DebugControl::setup, this)
}

override fun collectLogs() {
collectAndShareLogs(context)
override fun collectLogs(rwsId: String) {
collectAndShareLogs(context, rwsId)
}
}
54 changes: 49 additions & 5 deletions android/app/src/main/kotlin/io/rebble/cobble/log/LogSendingTask.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package io.rebble.cobble.log

import android.companion.CompanionDeviceManager
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.content.FileProvider
import io.rebble.cobble.CobbleApplication
import io.rebble.cobble.bluetooth.watchOrNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
Expand All @@ -13,20 +17,57 @@ import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Calendar
import java.util.TimeZone
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream


private fun generateDebugInfo(context: Context, rwsId: String): String {
val sdkVersion = Build.VERSION.SDK_INT
val device = Build.DEVICE
val model = Build.MODEL
val product = Build.PRODUCT
val manufacturer = Build.MANUFACTURER

val inj = (context.applicationContext as CobbleApplication).component
val connectionLooper = inj.createConnectionLooper()
val connectionState = connectionLooper.connectionState.value

val associatedDevices = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val deviceManager = context.getSystemService(CompanionDeviceManager::class.java)
deviceManager.associations
} else {
null
}
return """
SDK Version: $sdkVersion
Device: $device
Model: $model
Product: $product
Manufacturer: $manufacturer
Connection State: $connectionState
Associated devices: $associatedDevices
RWS ID:
$rwsId
""".trimIndent()
}

/**
* This should be eventually moved to flutter. Written it in Kotlin for now so we can use it while
* testing other things.
*/
fun collectAndShareLogs(context: Context) = GlobalScope.launch(Dispatchers.IO) {
fun collectAndShareLogs(context: Context, rwsId: String) = GlobalScope.launch(Dispatchers.IO) {
val logsFolder = File(context.cacheDir, "logs")

val targetFile = File(logsFolder, "logs.zip")
val date = LocalDateTime.now(ZoneId.of("UTC")).format(DateTimeFormatter.ISO_DATE_TIME)
val targetFile = File(logsFolder, "logs-${date}.zip")

var zipOutputStream: ZipOutputStream? = null
val debugInfo = generateDebugInfo(context, rwsId)
try {
zipOutputStream = ZipOutputStream(FileOutputStream(targetFile))
for (file in logsFolder.listFiles() ?: emptyArray()) {
Expand All @@ -44,6 +85,9 @@ fun collectAndShareLogs(context: Context) = GlobalScope.launch(Dispatchers.IO) {
inputStream.close()
zipOutputStream.closeEntry()
}
zipOutputStream.putNextEntry(ZipEntry("debug_info.txt"))
zipOutputStream.write(debugInfo.toByteArray())
zipOutputStream.closeEntry()
} catch (e: Exception) {
Timber.e(e, "Zip writing error")
} finally {
Expand All @@ -63,9 +107,9 @@ fun collectAndShareLogs(context: Context) = GlobalScope.launch(Dispatchers.IO) {
activityIntent.putExtra(Intent.EXTRA_STREAM, targetUri)
activityIntent.setType("application/octet-stream")

activityIntent.setClipData(ClipData.newUri(context.getContentResolver(),
activityIntent.clipData = ClipData.newUri(context.contentResolver,
"Cobble Logs",
targetUri))
targetUri)

activityIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4248,7 +4248,7 @@ public void error(Throwable error) {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface DebugControl {

void collectLogs();
void collectLogs(@NonNull String rwsId);

/** The codec used by DebugControl. */
static @NonNull MessageCodec<Object> getCodec() {
Expand All @@ -4264,8 +4264,10 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable DebugContr
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
String rwsIdArg = (String) args.get(0);
try {
api.collectLogs();
api.collectLogs(rwsIdArg);
wrapped.add(0, null);
}
catch (Throwable exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package io.rebble.cobble.bluetooth

import android.Manifest
import android.bluetooth.BluetoothDevice
import android.content.pm.PackageManager
import androidx.annotation.RequiresPermission
import androidx.core.app.ActivityCompat
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow

Expand All @@ -23,6 +25,15 @@ data class PebbleDevice (
emulated,
bluetoothDevice?.address ?: throw IllegalArgumentException()
)

override fun toString(): String {
val start = "< PebbleDevice emulated=$emulated, address=$address, bluetoothDevice=< BluetoothDevice address=${bluetoothDevice?.address}"
return try {
"$start, name=${bluetoothDevice?.name}, type=${bluetoothDevice?.type} > >"
} catch (e: SecurityException) {
"$start, name=unknown, type=unknown > >"
}
}
}

sealed class SingleConnectionStatus {
Expand Down
2 changes: 1 addition & 1 deletion ios/Runner/Pigeon/Pigeons.h
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ extern void IntentControlSetup(id<FlutterBinaryMessenger> binaryMessenger, NSObj
NSObject<FlutterMessageCodec> *DebugControlGetCodec(void);

@protocol DebugControl
- (void)collectLogsWithError:(FlutterError *_Nullable *_Nonnull)error;
- (void)collectLogsRwsId:(NSString *)rwsId error:(FlutterError *_Nullable *_Nonnull)error;
@end

extern void DebugControlSetup(id<FlutterBinaryMessenger> binaryMessenger, NSObject<DebugControl> *_Nullable api);
Expand Down
6 changes: 4 additions & 2 deletions ios/Runner/Pigeon/Pigeons.m
Original file line number Diff line number Diff line change
Expand Up @@ -2500,10 +2500,12 @@ void DebugControlSetup(id<FlutterBinaryMessenger> binaryMessenger, NSObject<Debu
binaryMessenger:binaryMessenger
codec:DebugControlGetCodec()];
if (api) {
NSCAssert([api respondsToSelector:@selector(collectLogsWithError:)], @"DebugControl api (%@) doesn't respond to @selector(collectLogsWithError:)", api);
NSCAssert([api respondsToSelector:@selector(collectLogsRwsId:error:)], @"DebugControl api (%@) doesn't respond to @selector(collectLogsRwsId:error:)", api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
NSArray *args = message;
NSString *arg_rwsId = GetNullableObjectAtIndex(args, 0);
FlutterError *error;
[api collectLogsWithError:&error];
[api collectLogsRwsId:arg_rwsId error:&error];
callback(wrapResult(nil, error));
}];
} else {
Expand Down
4 changes: 2 additions & 2 deletions lib/infrastructure/pigeons/pigeons.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2322,12 +2322,12 @@ class DebugControl {

static const MessageCodec<Object?> codec = StandardMessageCodec();

Future<void> collectLogs() async {
Future<void> collectLogs(String arg_rwsId) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.DebugControl.collectLogs', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(null) as List<Object?>?;
await channel.send(<Object?>[arg_rwsId]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
Expand Down
20 changes: 19 additions & 1 deletion lib/ui/devoptions/debug_options_page.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import 'package:cobble/domain/api/auth/auth.dart';
import 'package:cobble/domain/api/auth/user.dart';
import 'package:cobble/infrastructure/datasources/preferences.dart';
import 'package:cobble/infrastructure/datasources/web_services/auth.dart';
import 'package:cobble/infrastructure/pigeons/pigeons.g.dart';
import 'package:cobble/ui/common/components/cobble_button.dart';
import 'package:cobble/ui/router/cobble_scaffold.dart';
Expand Down Expand Up @@ -83,7 +86,22 @@ class DebugOptionsPage extends HookConsumerWidget implements CobbleScreen {
),
),
CobbleButton(
onPressed: () => debug.collectLogs(),
onPressed: () async {
AuthService auth = await ref.read(authServiceProvider.future);
User user = await auth.user;
String id = user.uid.toString();
String bootOverrideCount = user.bootOverrides?.length.toString() ?? "0";
String subscribed = user.isSubscribed.toString();
String timelineTtl = user.timelineTtl.toString();
debug.collectLogs(
"""
User ID: $id
Boot override count: $bootOverrideCount
Subscribed: $subscribed
Timeline TTL: $timelineTtl
""",
);
},
label: "Share application logs",
),
],
Expand Down
2 changes: 1 addition & 1 deletion pigeons/pigeons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ abstract class IntentControl {

@HostApi()
abstract class DebugControl {
void collectLogs();
void collectLogs(String rwsId);
}

@HostApi()
Expand Down

0 comments on commit eee9d18

Please sign in to comment.