본문으로 건너뛰기
cccskills
무료GitHub에 공개

tools-unity-sentry

Sentry Unity SDK integration patterns for error tracking, performance monitoring, transactions, spans, and custom instrumentation.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구Sentry
설치 방법 보기

포함된 파일(1)

  • SKILL.md14.9 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Sentry Unity SDK

Overview

Sentry provides error tracking and performance monitoring for Unity games. This skill covers SDK configuration, custom instrumentation, and best practices for mobile games.

When to Use

  • Setting up error tracking in Unity
  • Adding performance monitoring (transactions/spans)
  • Creating custom breadcrumbs and context
  • Debugging production crashes
  • Monitoring release health

Installation

// Via Unity Package Manager
// Add to manifest.json:
"io.sentry.unity": "1.5.0"

SDK Configuration

SentryOptionsConfiguration (ScriptableObject)

[CreateAssetMenu(fileName = "SentryOptions", menuName = "Sentry/Options")]
public class SentryOptionConfiguration : SentryOptionsConfiguration
{
    [SerializeField] private string _dsn;
    [SerializeField] private string _environment;
    [SerializeField] private float _tracesSampleRate = 0.2f;
    [SerializeField] private bool _enableLogDebouncing = true;
    
    public override void Configure(SentryUnityOptions options)
    {
        options.Dsn = _dsn;
        options.Environment = _environment;
        options.Release = $"{Application.version}+{BuildNumber}";
        
        // Performance monitoring
        options.TracesSampleRate = _tracesSampleRate;
        options.AutoStartupTraces = true;
        
        // Log debouncing (prevent spam)
        options.EnableLogDebouncing = _enableLogDebouncing;
        options.DebounceTimeError = TimeSpan.FromSeconds(1);
        
        // Deduplication
        options.DeduplicateMode = DeduplicateMode.All;
        
        // Callbacks
        options.SetBeforeSend(ModifyEvent);
        options.SetBeforeSendTransaction(ModifyTransaction);
        options.SetBeforeBreadcrumb(ModifyBreadcrumb);
        
        // Native crash handling
#if UNITY_ANDROID
        options.AndroidNativeInitializationType = NativeInitializationType.BuildTime;
#elif UNITY_IOS
        options.IosNativeInitializationType = NativeInitializationType.BuildTime;
#endif
    }
    
    private SentryEvent ModifyEvent(SentryEvent sentryEvent)
    {
        // Add custom context to all events
        sentryEvent.SetTag("quality_level", QualitySettings.GetQualityLevel().ToString());
        sentryEvent.SetTag("wifi", Application.internetReachability == 
            NetworkReachability.ReachableViaLocalAreaNetwork ? "true" : "false");
        return sentryEvent;
    }
    
    private SentryTransaction ModifyTransaction(SentryTransaction transaction)
    {
        // Add measurements to all transactions
        transaction.SetMeasurement("memory.used", GetUsedMemoryMB(), MeasurementUnit.Information.Megabyte);
        return transaction;
    }
    
    private Breadcrumb ModifyBreadcrumb(Breadcrumb breadcrumb)
    {
        // Filter unwanted breadcrumbs
        if (breadcrumb.Type == "http")
            return null; // Drop HTTP breadcrumbs
        return breadcrumb;
    }
}

User Context

Setting User Identity

public class SentryController
{
    public void SetUser(string userId, string email = null)
    {
        SentrySdk.ConfigureScope(scope =>
        {
            scope.User = new SentryUser
            {
                Id = userId,
                Email = email,
                Username = username
            };
        });
    }
    
    public void ClearUser()
    {
        SentrySdk.ConfigureScope(scope =>
        {
            scope.User = new SentryUser();
        });
    }
}

Setting Tags (Indexed, Searchable)

// Global tags (apply to all events)
SentrySdk.ConfigureScope(scope =>
{
    scope.SetTag("zone.id", currentZoneId);
    scope.SetTag("legend.id", activeLegendId);
    scope.SetTag("build.type", Debug.isDebugBuild ? "debug" : "release");
});

// Event-specific tags
SentrySdk.CaptureMessage("Custom event", scope =>
{
    scope.SetTag("feature", "combat");
});

Setting Extra Data (Non-indexed Context)

SentrySdk.ConfigureScope(scope =>
{
    scope.SetExtra("player_level", 42);
    scope.SetExtra("inventory_count", 150);
    scope.SetExtra("party_composition", new[] { "warrior", "mage", "healer" });
});

Transactions and Spans

Creating a Transaction

public async UniTask LoadZoneAsync(string zoneId)
{
    // Start transaction
    var transaction = SentrySdk.StartTransaction(
        name: $"Zone:Load:{zoneId}",
        operation: "zone.load"
    );
    
    // Set as current transaction for automatic span parenting
    SentrySdk.ConfigureScope(scope => scope.Transaction = transaction);
    
    try
    {
        // Add context
        transaction.SetTag("zone.id", zoneId);
        
        // Create child spans
        using (var span = transaction.StartChild("addressables.load"))
        {
            await LoadAddressablesAsync();
            span.SetMeasurement("asset.count", assetCount);
            span.Status = SpanStatus.Ok;
        }
        
        using (var span = transaction.StartChild("scene.load"))
        {
            await LoadSceneAsync();
            span.Status = SpanStatus.Ok;
        }
        
        transaction.Status = SpanStatus.Ok;
    }
    catch (Exception ex)
    {
        transaction.Status = SpanStatus.InternalError;
        SentrySdk.CaptureException(ex);
        throw;
    }
    finally
    {
        transaction.Finish();
    }
}

Nested Spans

public async UniTask ExecuteAbilityAsync(ISpan parentSpan)
{
    using var abilitySpan = parentSpan.StartChild("ability.execute");
    abilitySpan.SetTag("ability.id", _abilityId);
    
    try
    {
        using (var targetSpan = abilitySpan.StartChild("targeting.resolve"))
        {
            var targets = await ResolveTargets();
            targetSpan.SetMeasurement("target.count", targets.Count);
        }
        
        using (var effectSpan = abilitySpan.StartChild("effects.apply"))
        {
            foreach (var effect in _effects)
            {
                using var singleEffect = effectSpan.StartChild($"effect:{effect.Id}");
                await ApplyEffect(effect);
            }
        }
        
        abilitySpan.Status = SpanStatus.Ok;
    }
    catch
    {
        abilitySpan.Status = SpanStatus.InternalError;
        throw;
    }
}

Manual Span Timing

var span = transaction.StartChild("custom.operation");
span.SetTag("custom.tag", "value");

// Do work...

span.SetMeasurement("items.processed", itemCount);
span.Status = SpanStatus.Ok;
span.Finish(); // Must call if not using 'using'

Measurements

Adding Measurements to Transactions

// Numeric measurements with units
transaction.SetMeasurement("load.time", loadTimeMs, MeasurementUnit.Duration.Millisecond);
transaction.SetMeasurement("memory.used", usedMb, MeasurementUnit.Information.Megabyte);
transaction.SetMeasurement("fps.average", avgFps, MeasurementUnit.None);
transaction.SetMeasurement("asset.count", count, MeasurementUnit.None);

// Available units
MeasurementUnit.None
MeasurementUnit.Duration.Millisecond
MeasurementUnit.Duration.Second
MeasurementUnit.Information.Byte
MeasurementUnit.Information.Kilobyte
MeasurementUnit.Information.Megabyte
MeasurementUnit.Fraction.Percent

Breadcrumbs

Adding Breadcrumbs

// Simple message
SentrySdk.AddBreadcrumb("Player entered combat zone");

// With category and level
SentrySdk.AddBreadcrumb(
    message: "Zone loaded: Dunmiir",
    category: "navigation",
    level: BreadcrumbLevel.Info
);

// With custom data
SentrySdk.AddBreadcrumb(
    message: "Purchase completed",
    category: "monetization",
    level: BreadcrumbLevel.Info,
    data: new Dictionary<string, string>
    {
        { "product_id", productId },
        { "price", price.ToString() },
        { "currency", currency }
    }
);

// Different levels
BreadcrumbLevel.Debug
BreadcrumbLevel.Info
BreadcrumbLevel.Warning
BreadcrumbLevel.Error
BreadcrumbLevel.Critical

Automatic Breadcrumb Categories

// Recommended categories for games:
"navigation"    // Zone changes, scene loads
"combat"        // Combat events
"ui"            // UI state changes
"monetization"  // Purchase events
"quest"         // Quest progress
"performance"   // Performance events
"network"       // Network operations
"state"         // State machine transitions

Error Capture

Capture Exception

try
{
    RiskyOperation();
}
catch (Exception ex)
{
    SentrySdk.CaptureException(ex);
    // or with extra context
    SentrySdk.CaptureException(ex, scope =>
    {
        scope.SetTag("operation", "risky_operation");
        scope.SetExtra("input_data", inputData);
    });
}

Capture Message

// Simple message
SentrySdk.CaptureMessage("Unexpected state reached");

// With level
SentrySdk.CaptureMessage("Memory warning triggered", SentryLevel.Warning);

// With context
SentrySdk.CaptureMessage("Combat desync detected", scope =>
{
    scope.SetTag("combat.id", combatId);
    scope.SetExtra("player_state", playerState);
    scope.Level = SentryLevel.Error;
});

Custom Exception Context

public class GameplayException : Exception
{
    public string ZoneId { get; }
    public string AbilityId { get; }
    
    public GameplayException(string message, string zoneId, string abilityId) 
        : base(message)
    {
        ZoneId = zoneId;
        AbilityId = abilityId;
    }
}

// Capture with context
try
{
    ExecuteAbility();
}
catch (GameplayException ex)
{
    SentrySdk.CaptureException(ex, scope =>
    {
        scope.SetTag("zone.id", ex.ZoneId);
        scope.SetTag("ability.id", ex.AbilityId);
    });
}

Performance Monitoring Patterns

Continuous Profiling

public class PerformanceMonitor
{
    private readonly FrameTiming[] _frameTimings = new FrameTiming[1];
    
    public void CaptureFrame()
    {
        var transaction = SentrySdk.StartTransaction("Gather Metrics", "performance.metrics");
        
        // Frame timing
        if (FrameTimingManager.IsFeatureEnabled())
        {
            FrameTimingManager.CaptureFrameTimings();
            var count = FrameTimingManager.GetLatestTimings(1, _frameTimings);
            
            if (count > 0)
            {
                transaction.SetMeasurement("frame.cpu", _frameTimings[0].cpuFrameTime, 
                    MeasurementUnit.Duration.Millisecond);
                transaction.SetMeasurement("frame.gpu", _frameTimings[0].gpuFrameTime,
                    MeasurementUnit.Duration.Millisecond);
            }
        }
        
        // Memory
        var usedMemory = Profiler.GetTotalAllocatedMemoryLong() / 1_000_000f;
        transaction.SetMeasurement("memory.used", usedMemory, MeasurementUnit.Information.Megabyte);
        
        // FPS
        transaction.SetMeasurement("fps", 1f / Time.deltaTime, MeasurementUnit.None);
        
        // Context
        transaction.SetTag("zone.id", CurrentZone);
        transaction.SetTag("in_combat", InCombat.ToString());
        
        transaction.Finish();
    }
}

Hitch Detection

public void DetectHitch(float frameTimeMs)
{
    if (frameTimeMs > 100f) // Severe hitch
    {
        SentrySdk.AddBreadcrumb(
            message: $"Severe hitch: {frameTimeMs:F1}ms",
            category: "performance",
            level: BreadcrumbLevel.Error,
            data: new Dictionary<string, string>
            {
                { "zone", _currentZone },
                { "in_combat", _inCombat.ToString() },
                { "activity", _currentActivity }
            }
        );
    }
    else if (frameTimeMs > 50f) // Major hitch
    {
        SentrySdk.AddBreadcrumb(
            message: $"Major hitch: {frameTimeMs:F1}ms",
            category: "performance",
            level: BreadcrumbLevel.Warning
        );
    }
}

Release Health

Release Configuration

// In SentryOptionsConfiguration
options.Release = $"{Application.version}+{Changelist}";
options.Environment = "production"; // or "staging", "development"

Session Tracking

// Sessions are tracked automatically, but you can add context
public void OnSessionStart()
{
    SentrySdk.ConfigureScope(scope =>
    {
        scope.SetTag("session.type", isFirstSession ? "new" : "returning");
        scope.SetTag("install.age", installAgeDays.ToString());
    });
}

Mobile-Specific Considerations

Memory Warnings

private void OnEnable()
{
    Application.lowMemory += OnLowMemory;
}

private void OnLowMemory()
{
    SentrySdk.AddBreadcrumb(
        message: "Low memory warning",
        category: "performance",
        level: BreadcrumbLevel.Warning,
        data: new Dictionary<string, string>
        {
            { "used_mb", GetUsedMemoryMB().ToString() },
            { "zone", _currentZone }
        }
    );
}

Background/Foreground

private void OnApplicationPause(bool paused)
{
    if (paused)
    {
        SentrySdk.AddBreadcrumb("App backgrounded", "lifecycle");
        SentrySdk.FlushAsync(TimeSpan.FromSeconds(2)).Forget();
    }
    else
    {
        SentrySdk.AddBreadcrumb("App foregrounded", "lifecycle");
    }
}

Network Connectivity

SentrySdk.ConfigureScope(scope =>
{
    var reachability = Application.internetReachability;
    scope.SetTag("network.type", reachability switch
    {
        NetworkReachability.NotReachable => "none",
        NetworkReachability.ReachableViaLocalAreaNetwork => "wifi",
        NetworkReachability.ReachableViaCarrierDataNetwork => "cellular",
        _ => "unknown"
    });
});

Debug Symbol Upload

Unity Cloud Build / CI

# Upload debug symbols after build
sentry-cli debug-files upload \
  --org your-org \
  --project your-project \
  ./Library/Bee/artifacts/

# For iOS
sentry-cli debug-files upload \
  --org your-org \
  --project your-project \
  ./Builds/iOS/

IL2CPP Symbols

# Upload IL2CPP line number mappings
sentry-cli debug-files upload \
  --include-sources \
  ./Library/Bee/artifacts/il2cpp/

Best Practices

  1. Set user context early - As soon as authentication completes
  2. Use transactions for key flows - Zone loads, combat, purchases
  3. Add breadcrumbs liberally - Help debug crash context
  4. Sample transactions - 0.1-0.2 for production, 1.0 for debugging
  5. Use meaningful operation names - zone.load, combat.ability, ui.navigate
  6. Add measurements - Memory, FPS, timing data on transactions
  7. Filter noisy breadcrumbs - HTTP spam, frequent events
  8. Upload debug symbols - Critical for native crash symbolication
  9. Test in Editor - Enable CaptureInEditor during development

Troubleshooting

IssueSolution
Events not appearingCheck DSN, network, sample rate
Missing stack tracesUpload debug symbols
Too many eventsIncrease debounce time, filter
Performance impactReduce sample rate, async flush
Native crashes unsymbolicatedUpload dSYM/symbols

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

adopt

무료

Brownfield onboarding — audits existing project artifacts for template format compliance (not just existence), classifies gaps by impact, and produces a numbered migration plan. Run this when joining an in-progress project or upgrading from an older template version. Distinct from /project-stage-detect (which checks what exists) — this checks whether what exists will actually work with the template's skills.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

Creates an Architecture Decision Record (ADR) documenting a significant technical decision, its context, alternatives considered, and consequences. Every major technical choice should have an ADR.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

Validates completeness and consistency of the project architecture against all GDDs. Builds a traceability matrix mapping every GDD technical requirement to ADRs, identifies coverage gaps, detects cross-ADR conflicts, verifies engine compatibility consistency across all decisions, and produces a PASS/CONCERNS/FAIL verdict. The architecture equivalent of /design-review.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

art-bible

무료

Guided, section-by-section Art Bible authoring. Creates the visual identity specification that gates all asset production. Run after /brainstorm is approved and before /map-systems or any GDD authoring begins.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

Audits game assets for compliance with naming conventions, file size budgets, format standards, and pipeline requirements. Identifies orphaned assets, missing references, and standard violations.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

Generate per-asset visual specifications and AI generation prompts from GDDs, level docs, or character profiles. Produces structured spec files and updates the master asset manifest. Run after art bible and GDD/level design are approved, before production begins.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

IdoCohen560의 스킬 모두 보기

이 스킬의 문제 신고