Skip to content

Commit b4e9904

Browse files
committed
Merge remote-tracking branch 'origin/dev-logger-fix' into ben/fixes
2 parents 62d9428 + e169066 commit b4e9904

File tree

4 files changed

+189
-2
lines changed

4 files changed

+189
-2
lines changed

CLAUDE.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
This is a Unity plugin for BetaHub bug reporting that enables in-game bug submissions with video recording, screenshot capture, and log collection. The plugin is distributed as a Unity Package Manager (UPM) package.
8+
9+
## Architecture
10+
11+
### Core Components
12+
13+
- **GameRecorder** (`Runtime/Scripts/GameRecorder.cs`): Handles video recording using FFmpeg with optimized GPU-based capture
14+
- **BugReportUI** (`Runtime/Scripts/BugReportUI.cs`): Main UI component for bug report submission form
15+
- **Issue** (`Runtime/Scripts/Issue.cs`): Data model representing bug reports and their upload state
16+
- **VideoEncoder** (`Runtime/Scripts/VideoEncoder.cs`): FFmpeg wrapper for video encoding with segmented recording
17+
- **Process Wrappers**: Platform-specific process handling for FFmpeg
18+
- **IProcessWrapper** (`Runtime/Scripts/IProcessWrapper.cs`): Interface for process abstraction
19+
- **DotNetProcessWrapper** (`Runtime/Scripts/DotNetProcessWrapper.cs`): .NET/Mono implementation
20+
- **NativeProcessWrapper** (`Runtime/Scripts/NativeProcessWrapper.cs`): IL2CPP implementation using native library
21+
22+
### Assembly Definitions
23+
24+
- **Runtime**: `io.betahub.bugreporter.asmdef` - Main plugin runtime assembly
25+
- **Editor**: `io.betahub.bugreporter.editor.asmdef` - Editor-only features (FFmpeg downloader)
26+
27+
### Platform Support
28+
29+
The plugin supports Windows, macOS, and Linux with special handling for IL2CPP builds:
30+
- **Mono/.NET**: Uses standard .NET Process class
31+
- **IL2CPP**: Requires `ENABLE_BETAHUB_FFMPEG` scripting symbol and native process wrapper libraries in `Plugins/` directories
32+
33+
### Native Libraries
34+
35+
Platform-specific native libraries for IL2CPP FFmpeg support:
36+
- Windows: `Plugins/x86_64/betahub_process_wrapper.dll`
37+
- macOS: `Plugins/macOS/libbetahub_process_wrapper.dylib`
38+
- Linux: `Plugins/Linux/x86_64/libbetahub_process_wrapper.so`
39+
40+
## Development
41+
42+
### Unity Package Structure
43+
44+
This project follows Unity Package Manager conventions:
45+
- `package.json`: Package metadata and dependencies
46+
- `Runtime/`: Runtime scripts and assets
47+
- `Editor/`: Editor-only scripts
48+
- `Samples~/`: Sample scenes and scripts
49+
- Assembly definition files (`.asmdef`) organize code into separate assemblies
50+
51+
### Testing
52+
53+
The plugin includes a working demo scene in `Samples~/ExampleScene/` with:
54+
- Sample scene setup
55+
- Example integration with `RotateCube.cs` script
56+
- Pre-configured demo project settings for immediate testing
57+
58+
### FFmpeg Integration
59+
60+
The plugin automatically downloads FFmpeg binaries through the editor script `FfmpegDownloader.cs`. Video recording is implemented with:
61+
- Segmented recording (10-second segments, 60-second rolling window)
62+
- GPU-optimized capture using RenderTextures
63+
- Cross-platform process wrapper abstraction
64+
- Error handling and retry logic for file operations
65+
66+
## Configuration
67+
68+
### Required Settings for IL2CPP
69+
70+
When building with IL2CPP scripting backend:
71+
1. Define `ENABLE_BETAHUB_FFMPEG` in Player Settings > Scripting Define Symbols
72+
2. Ensure native process wrapper libraries are included in build
73+
74+
### Demo Project
75+
76+
The plugin comes pre-configured with demo project credentials (`DEMO_PROJECT_ID = "pr-5287510306"`) for immediate testing. Reports submitted to the demo project are only visible to the submitter via email links.
77+
78+
## Development Notes
79+
80+
- Do not generate meta files, let Unity do this
81+
- Use `ruby validate_prefab.rb <prefab_path>` to validate Unity prefabs for duplicate IDs and broken references

Runtime/Scripts/BugReportUI.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ private IEnumerator SubmitBugReportCoroutine()
402402
// skip if size over 200MB
403403
if (new FileInfo(_logger.LogPath).Length < 200 * 1024 * 1024)
404404
{
405-
logFiles.Add(new Issue.LogFileReference { path = _logger.LogPath, removeAfterUpload = false });
405+
logFiles.Add(new Issue.LogFileReference { logger = _logger, removeAfterUpload = false });
406406
}
407407
}
408408
}

Runtime/Scripts/Issue.cs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public struct LogFileReference
4646
{
4747
public string path;
4848
public bool removeAfterUpload;
49+
public Logger logger; // Optional: if set, use logger.ReadLogFileBytes() instead of path
4950
}
5051

5152
public struct ScreenshotFileReference
@@ -381,8 +382,25 @@ private IEnumerator PostScreenshot(ScreenshotFileReference screenshot)
381382

382383
private IEnumerator PostLogFile(LogFileReference logFile)
383384
{
384-
if (File.Exists(logFile.path))
385+
if (logFile.logger != null)
385386
{
387+
// Use logger's safe read method to avoid sharing violations
388+
Debug.Log("Reading BetaHub log file safely using Logger instance");
389+
byte[] fileData = logFile.logger.ReadLogFileBytes();
390+
391+
if (fileData != null)
392+
{
393+
string fileName = Path.GetFileName(logFile.logger.LogPath) ?? "BH_Player.log";
394+
yield return UploadStringAsFile("log_files", "log_file[file]", fileData, fileName, "text/plain");
395+
}
396+
else
397+
{
398+
Debug.LogError("Failed to read log file data from Logger instance");
399+
}
400+
}
401+
else if (File.Exists(logFile.path))
402+
{
403+
// Original file path logic
386404
yield return UploadFile("log_files", "log_file[file]", logFile.path, "text/plain");
387405

388406
if (logFile.removeAfterUpload)
@@ -459,6 +477,37 @@ private IEnumerator UploadFile(string endpoint, string fieldName, string filePat
459477
}
460478
}
461479

480+
private IEnumerator UploadStringAsFile(string endpoint, string fieldName, byte[] fileData, string fileName, string contentType)
481+
{
482+
if (fileData == null)
483+
{
484+
Debug.LogError($"Cannot upload {fileName}: file data is null");
485+
yield break;
486+
}
487+
488+
WWWForm form = new WWWForm();
489+
form.AddBinaryData(fieldName, fileData, fileName, contentType);
490+
491+
string url = $"{_betahubEndpoint}projects/{_projectId}/issues/g-{Id}/{endpoint}";
492+
using (UnityWebRequest www = UnityWebRequest.Post(url, form))
493+
{
494+
www.SetRequestHeader("Authorization", "Bearer " + _updateIssueAuthToken);
495+
www.SetRequestHeader("BetaHub-Project-ID", _projectId);
496+
www.SetRequestHeader("Accept", "application/json");
497+
498+
yield return www.SendWebRequest();
499+
500+
if (www.result != UnityWebRequest.Result.Success)
501+
{
502+
Debug.LogError($"Error uploading {fileName}: {www.error}");
503+
}
504+
else
505+
{
506+
Debug.Log($"{fileName} uploaded successfully!");
507+
}
508+
}
509+
}
510+
462511
private IEnumerator PublishNow()
463512
{
464513
// Ensure we have valid parameters before proceeding

Runtime/Scripts/Logger.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,63 @@ public void ResumeLogging()
156156
}
157157
}
158158

159+
/// <summary>
160+
/// Safely reads the entire log file by temporarily closing the file stream.
161+
/// This prevents file sharing violations when other parts of the application need to read the log.
162+
/// </summary>
163+
/// <returns>The complete log file content as a byte array, or null if an error occurs</returns>
164+
public byte[] ReadLogFileBytes()
165+
{
166+
if (disposed || string.IsNullOrEmpty(_logPath))
167+
{
168+
Debug.LogWarning("Cannot read log file: Logger is disposed or log path is not set");
169+
return null;
170+
}
171+
172+
lock (lockObject)
173+
{
174+
try
175+
{
176+
// First, flush any buffered data
177+
FlushBuffer();
178+
179+
// Temporarily close the file streams
180+
writer?.Dispose();
181+
fileStream?.Dispose();
182+
writer = null;
183+
fileStream = null;
184+
185+
// Now we can safely read the file since we've closed our handle
186+
byte[] fileData = null;
187+
if (File.Exists(_logPath))
188+
{
189+
fileData = File.ReadAllBytes(_logPath);
190+
}
191+
192+
// Reopen the file streams to continue logging
193+
InitializeFileStream();
194+
195+
return fileData;
196+
}
197+
catch (Exception e)
198+
{
199+
Debug.LogError("Error reading log file: " + e.Message);
200+
201+
// Ensure we reopen the file streams even if reading failed
202+
try
203+
{
204+
InitializeFileStream();
205+
}
206+
catch (Exception reopenEx)
207+
{
208+
Debug.LogError("Error reopening log file after failed read: " + reopenEx.Message);
209+
}
210+
211+
return null;
212+
}
213+
}
214+
}
215+
159216
public void Dispose()
160217
{
161218
if (disposed) return;

0 commit comments

Comments
 (0)