روش‌هایی برای بهبود قابلیت دیباگ بسته‌های NuGet
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: سه دقیقه

عموما بسته‌های نیوگت تولید شده، قابلیت دیباگ ضعیفی را دارند. برای بالابردن بهبود تجربه‌ی کاربری آن‌ها می‌توان توزیع فایل‌های PDB و فعالسازی قابلیت Source Link را به آن‌ها اضافه کرد.


فعالسازی توزیع فایل‌های PDB به همراه بسته‌های NuGet

وجود فایل‌های PDB، برای اجرای برنامه‌ها ضرورتی ندارند؛ اما اگر ارائه شوند، به کمک آن‌ها می‌توان گزارش‌های استثناءهای بسیار کاملتری را به همراه نام فایل و شماره سطرهای مرتبط موجود در Exception.StackTrace، مشاهده کرد.
پیشتر توسعه دهندگان بسته‌های NuGet، فایل‌های PDB را خودشان با تعریف یک سری include در فایل مشخصات بسته، به فایل نهایی تولیدی اضافه می‌کردند. اما این روزها با ارائه‌ی «NuGet.org Symbol Server»، دیگر افزودن فایل‌های PDB به بسته‌های nupkg توصیه نمی‌شود. چون حجم نهایی و زمان بازیابی بسته‌ها را بیش از اندازه افزایش می‌دهند؛ خصوصا اگر مصرف کننده‌ای قصد دیباگ آن‌ها را نداشته باشد.
راه حل جدید توصیه شده، ارائه‌ی فایل‌های ویژه‌ی snupkg. در کنار بسته‌های nupkg. معمولی است که حاوی فایل‌های PDB متناظر با بسته‌ی اصلی NuGet هستند.

برای فعالسازی آن‌ها در پروژه‌های NET Core. بسته‌های نیوگت خود تنها کافی است دو تنظیم زیر را به فایل csproj اضافه کنید:
<PropertyGroup>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
در این حالت پس از ساخت بسته‌ی نیوگت توسط دستور «dotnet pack -c release»، دو فایل با پسوندهای snupkg و nupkg تولید می‌شوند که باید هر دو را به سایت NuGet ارسال کرد.

در سمت مصرف کننده، IDE شما باید برای کار با این Symbol Server تنظیم شده باشد.


فعالسازی تولید Source Link

وجود PDBها جهت دیباگ بسته‌های ارائه شده بسیار مفیدند؛ اما اگر بر روی کدهای نهایی بهینه سازی صورت گرفته باشد، ممکن است اطلاعات دیباگ آن‌ها با کد اصلی تطابق پیدا نکنند. برای بهبود این وضعیت و ارتقاء آن به یک سطح بالاتر، مفهوم source link ارائه شده‌است که مستقل است از نوع زبان و همچنین سورس کنترل.
کار سورس‌لینک، افزودن متادیتای سورس کنترل انتخابی، به بسته‌ی نهایی تولید شده‌است. به این صورت می‌توان سورس کامل و متناظر با قطعه کد بسته و کتابخانه‌ی ثالث در حال دیباگ را در IDE خود داشت و با آن به نحو متداولی کار کرد. یعنی سورس لینک، قابلیت «Step Into" the source code"» را مهیا می‌کند. متادیتای اضافه شده، دقیقا مشخص می‌کند که بسته‌ی تولیدی نهایی، متناظر با کدام commit سورس کنترل است. به این ترتیب دقیقا می‌توان به کدهای همان commit ای که بسته بر اساس آن کامپایل شده‌است، در IDE خود دسترسی یافت.
این قابلیت از Visual Studio 15.3 به بعد در اختیار کاربران آن است (گزینه‌ی Enable Source Server Support، در قسمت Debug/General آن باید فعال شود). همچنین Rider و VSCode نیز از آن پشتیبانی می‌کنند. برای Rider باید در قسمت Tools/External symbols، گزینه‌های Use sources from symbol files when available و Allow downloading symbols from remote locations را فعال کنید.
همچنین برای فعالسازی آن در فایل csproj بسته‌ی نیوگت خود می‌توانید تنظیمات زیر را اضافه کنید:
<PropertyGroup>
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
البته در اینجا پروایدر مخصوص GitHub را مشاهده می‌کنید (اگر مخزن کد بسته‌ی شما بر روی آن قرار دارد) و همچنین سایر پروایدرهای مخصوص سورس کنترل‌های دیگر مانند Azure DevOps/VSTS نیز برای آن تهیه شده‌اند.


روش فعالسازی Source Link در پروژه‌ی VSCode

اگر از VSCode استفاده می‌کنید، نیاز است تنظیمات زیر را به قسمت configurations فایل launch.json خود اضافه کنید تا قابلیت «Step Into" the source code"» بسته‌های نیوگتی که از SourceLink پشتیبانی می‌کنند، با فشردن دکمه‌ی F11 در حین دیباگ، فعال شود:
"justMyCode": false,
"symbolOptions": {
   "searchMicrosoftSymbolServer": true
},
"suppressJITOptimizations": true,
"env": {
   "COMPlus_ZapDisable": "1"
}
  • #
    ‫۳ سال و ۱۰ ماه قبل، یکشنبه ۱۱ آبان ۱۳۹۹، ساعت ۱۵:۵۵
    ممنون بابت مطلب خوبتون
    الان امکان source link روی پکیج EFCoreSecondLevelCacheInterceptor هم توسط خودتون اعمال شده ولی من نتونستم موقع دیباگ Step Into کنم نه با VS و نه با VSCode (تنظیمات لازم رو هم اعمال کردم)
    خودتون تونستین نتیجه بگیرین از این روش؟
    سورس پکیج‌های ASPNET Core رو هم وقتی داخلشو دیباگ میکنم بعضی وقتا (ضمن اجرای اون کد) از روی خطوط میپره و یا مقایر متغیر‌ها رو نشون نمیده که به احتمال زیاد دلیلش باید بهینه سازی‌های صورت گرفته باشه که در این صورت یعنی از SourceLink برای بازیابی سورس اصلی بهره نبرده؟
    من قبلا بحث debugging کتابخانه‌های خودم رو توسط تنظیم زیر انجام میدادم و به خوبی جواب می‌داد. البته شاید بهینه نباشه (چون هم pdb‌ها داخلش embed میشن و هم optimize رو جهت debugging  experience بهتر غیر فعال کردم) ولی حداقلش اینه کار میکنه (بر خلاف روش‌های اصولیش که من خیلی هم بررسی کردم ولی نتونستم جواب درست و درمونی ازشون بگیرم)
    <DebugType>embedded</DebugType>
    <Optimize>false</Optimize>

      • #
        ‫۳ سال و ۱۰ ماه قبل، یکشنبه ۱۱ آبان ۱۳۹۹، ساعت ۲۳:۰۶
        راستش اینا که سهله، قبلا مقالات جامع‌تری رو مطالعه کردم و ایشو‌های sourcelink داخل ریپوی خودش و dotnetsdk رو زیرُ رو کردم.
        واقعیت اینه که این قابلیت آنچنان stable و قابل استفاده نیست. توی ورژن‌های مختلف sdk مشکلات متعددی براش پیش اومده و workaround‌های متفاوتی هم براش دادن. 
        این موارد رو میشه توی issue معروف این قضیه که بعد از گذشت 3 سال هنوز باز هست مشاهده کنین. انصافا من از بعضی از workaround هاش هم جواب گرفتم ولی همچنان راه سر راست و درست و درمون نداره مگر با اون کدی که فرستادم.
        پکیج EFCoreSecondLevelCacheInterceptor  رو روی چندین سیستم هم امتحان کردم و جواب نداد و نهایتا با قرار دادن کد زیر توی csproj (که یکی از workaround‌های همون issue هست) جواب داد. البته اونم رو فقط رو net5.0!
        <!-- https://github.com/dotnet/sdk/issues/1458#issuecomment-420456386 -->
        <Target Name="_ResolveCopyLocalNuGetPackagePdbsAndXml" Condition="$(CopyLocalLockFileAssemblies) == true" AfterTargets="ResolveReferences">
        <ItemGroup>
          <ReferenceCopyLocalPaths
        Include="@(ReferenceCopyLocalPaths->'%(RootDir)%(Directory)%(Filename).pdb')"
        Condition="'%(ReferenceCopyLocalPaths.NuGetPackageId)' != '' and Exists('%(RootDir)%(Directory)%(Filename).pdb')" />
          <ReferenceCopyLocalPaths
        Include="@(ReferenceCopyLocalPaths->'%(RootDir)%(Directory)%(Filename).xml')"
        Condition="'%(ReferenceCopyLocalPaths.NuGetPackageId)' != '' and Exists('%(RootDir)%(Directory)%(Filename).xml')" />
        </ItemGroup>
        </Target>
        در کل چون این یه موضوعی هست که به چندین تیم و محصول وابسته هست (تیم های 
        dotnet SDK  و VS و Nuget و  SourceLink) و نیاز به هماهنگی هست متاسفانه خیلی کند پیش میرن و در حال حاضر پلن کردند واسه  milestone دات نت 6 که انشالا برطرف بشه
        • #
          ‫۳ سال و ۱۰ ماه قبل، دوشنبه ۱۲ آبان ۱۳۹۹، ساعت ۰۰:۵۶
          - کتابخانه‌ای که ذکر کردید، از روش symbol server نیوگت استفاده می‌کند (که در بحث جاری مطرح شده) و نه قرار دادن فایل‌های pdb در بسته‌ی نیوگت. به همین جهت ارتباطی به issue ای که ارسال کردید و در مورد pdbهای embedded هست، ندارد و فایل‌های pdb دریافتی از symbol server، در پوشه‌ی bin کپی نمی‌شوند و در صورت دریافت، سراسری هستند (ذخیره در کش عمومی سیستم و بارگذاری مجدد از همان کش).
          - هدف از source link این هست که بتوان قطعه کد کتابخانه‌ی ثالثی را در حین دیباگ مشاهده کرد. هدف از pdb دریافتی از nuget هم این است که اگر در حین کار با کتابخانه‌ای به استثنائی رسیدید، اطلاعات دیباگ بیشتری مانند شماره سطر کدهای مرتبط با آن کتابخانه را نمایش دهد و هر دو مورد هم بدون هیچ تنظیم اضافه‌تری در فایل csproj، با VSCode کار می‌کنند.

          یک مثال با VSCode:
          فایل launch.json پروژه به این صورت تغییر کرد (بر اساس توضیحات انتهای مطلب):
          {
              // Use IntelliSense to find out which attributes exist for C# debugging
              // Use hover for the description of the existing attributes
              // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
              "version": "0.2.0",
              "configurations": [
                  {
                      "name": ".NET Core Launch (console)",
                      "type": "coreclr",
                      "request": "launch",
                      "preLaunchTask": "build",
                      // If you have changed target frameworks, make sure to update the program path.
                      "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/EFCoreDbFunctionsSample.dll",
                      "args": [],
                      "cwd": "${workspaceFolder}",
                      // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
                      "console": "internalConsole",
                      "stopAtEntry": false,
                      "justMyCode": false,
                      "symbolOptions": {
                          "searchMicrosoftSymbolServer": true
                      },
                      "suppressJITOptimizations": true,
                      "env": {
                          "COMPlus_ZapDisable": "1"
                      }
                  },
                  {
                      "name": ".NET Core Attach",
                      "type": "coreclr",
                      "request": "attach",
                      "processId": "${command:pickProcess}"
                  }
              ]
          }
          در این زمان با فشردن دکمه‌ی F5 در VSCode، کار دریافت symbols از symbols server شروع می‌شود (و کمی طول می‌کشد و در لاگ پروژه، مراحل آن کاملا مشخص هست). در این حالت فایل‌های pdb را هم داخل پوشه‌ی bin\Debug\netcoreapp3.1 کپی نمی‌کند و در کش سراسری nuget در سیستم قرار می‌دهد تا به ازای هر پروژه، این اطلاعات تکراری حجیم (به ازای هر dll مرتبط با پروژه، یک فایل pdb حجیم از symbol server دریافت خواهد شد)، دریافت نشوند:
          Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.8\System.Private.CoreLib.dll'. Symbols loaded.
          Loaded 'D:\Prog\1399\EFCoreDbFunctionsSample\bin\Debug\netcoreapp3.1\EFCoreDbFunctionsSample.dll'. Symbols loaded.
          .
          .
          .
          Loaded 'D:\Prog\1399\EFCoreDbFunctionsSample\bin\Debug\netcoreapp3.1\EFCoreSecondLevelCacheInterceptor.dll'. Symbols loaded.
          .
          .
          .
          همانطور که مشاهده می‌کنید، Symbols مربوط به کتابخانه‌ی ثالث استفاده شده هم بارگذاری شده‌اند.

          در مورد سورس لینک:
          قرار دادن یک break-point روی یک سطر:


          و سپس زمانیکه در حالت دیباگ (همان فشردن دکمه‌ی F5 در VSCode)، به این سطر رسیدیم، فشردن دکمه‌ی F11، تا سورس متناظر بارگذاری شود:

          • #
            ‫۳ سال و ۱۰ ماه قبل، دوشنبه ۱۲ آبان ۱۳۹۹، ساعت ۰۱:۲۹
            ممنون البته من منظور با VS بود نه VS Code و بلاخره مشکل رو پیدا کردم. ایراد این بود که Symbols‌ها توی VS لود نمیشد و علتش هم غیر فعال بودن Nuget Symbols Server بود (و جالب اینجاست که ظاهرا به صورت پیشفرض غیرفعاله!)

            در اینجا میخوام تمام نکاتی که لازمه تا Source Link توی VS بدرستی کار بکنه رو لیست کنم

  • #
    ‫۳ سال و ۸ ماه قبل، پنجشنبه ۲۰ آذر ۱۳۹۹، ساعت ۱۲:۱۰
    یک نکته‌ی تکمیلی
    مایکروسافت توصیه کرده که از روش قرار دادن اطلاعات دیباگ در بسته‌ی نیوگت نهایی استفاده کنید:
    <!-- Embed source files that are not tracked by the source control manager in the PDB -->
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
    
    <!-- Recommended: Embed symbols containing Source Link in the main file (exe/dll) -->
    <DebugType>embedded</DebugType>
  • #
    ‫۳ سال و ۸ ماه قبل، دوشنبه ۱۵ دی ۱۳۹۹، ساعت ۱۴:۰۱
    محدودیت با مخزن  خصوصی  گیتلب (Gitlab  Private   Repository):
    در حال حاضر این روش با مخازن خصوصی گیتلب بدلیل تفاوت نحوه احراز هویت گیتلب با دیگر CodeHosting‌ها سازگار نیست. و به هنگام دیباگ به جای فایل مورد نظر، صفحه لاگین را برمیگرداند Issue #281 
    یک راه حل برای ویژوال استودیو 2019: ایجاد Session برای گیتلب توسط مرورگر داخلی ویژوال استودیو
    از طریق View -> Other Windows -> Web Browser به گیتلب خود لاگین کنید.
    راه حل برای Gitlab Self-Hosted: