LogScope.NET

A lightweight logging framework designed for tracing, profiling, and logging critical parts of your code.

LogScope.NET uses a scope-based approach where each scope represents a method or code section. Built on the IDisposable pattern, it automatically tracks execution time and produces hierarchical output that clearly shows your application's execution flow.

Core Features

Scope-Based Logging

Uses IDisposable pattern where Dispose marks scope completion with automatic timing.

Performance Tracking

Automatically measures and reports execution time for each scope.

Nested Scopes

Create scopes within scopes with hierarchical indented output.

Easy Integration

Works with dependency injection and Microsoft.Extensions.Logging.

Installation

Core Library:

dotnet add package DevInstance.LogScope

With Dependency Injection:

dotnet add package DevInstance.LogScope.NET

Microsoft.Extensions.Logging Integration:

dotnet add package DevInstance.LogScope.Extensions.MicrosoftLogger

Log Levels

Level Description Use Case
NOLOG Logging disabled Production with no logging
ERROR Error messages only Production environments
WARNING Warnings and errors Production with diagnostics
INFO Informational messages General monitoring
DEBUG All messages (most verbose) Development and debugging
License

LogScope.NET is released under the Apache-2.0 License.

Documentation

LogScope.NET Usage Guide

A comprehensive guide to using LogScope.NET for tracing, profiling, and logging in .NET applications.

Table of Contents


Installation & Setup

Requirements

  • .NET 10 SDK or later

Package Installation

Choose the package that fits your needs:

# Core library
dotnet add package DevInstance.LogScope

# With dependency injection support
dotnet add package DevInstance.LogScope.NET

# Microsoft.Extensions.Logging integration
dotnet add package DevInstance.LogScope.Extensions.MicrosoftLogger

Quick Start

With Dependency Injection:

using DevInstance.LogScope;

var builder = WebApplication.CreateBuilder(args);

// Add scope logging with console output
builder.Services.AddConsoleScopeLogging(LogLevel.DEBUG);

var app = builder.Build();

Console Application (Non-DI):

using DevInstance.LogScope;

var manager = DefaultScopeLogFactory.CreateConsoleLogger(LogLevel.DEBUG);
var log = manager.CreateLogger(this);

Core Concepts

What is a Scope?

A scope represents a method or code section that you want to trace. LogScope uses the IDisposable pattern where:

  • Creating a scope marks the entry point
  • Dispose() (end of using block) marks the exit point
  • Execution time is automatically measured between entry and exit

Why Use Scopes?

LogScope provides more streamlined coding compared to conventional logging APIs:

Traditional Logging:

public void ProcessOrder(Order order)
{
    _logger.LogDebug("ProcessOrder started");
    var stopwatch = Stopwatch.StartNew();

    try
    {
        // Process order...
        _logger.LogDebug("Processing order {OrderId}", order.Id);
    }
    finally
    {
        stopwatch.Stop();
        _logger.LogDebug("ProcessOrder completed in {Elapsed}ms", stopwatch.ElapsedMilliseconds);
    }
}

With LogScope:

public void ProcessOrder(Order order)
{
    using var scope = log.DebugScope();

    // Process order...
    scope.D($"Processing order {order.Id}");
}

Basic Usage

Creating a Logger

Inject IScopeManager and create a logger in your class:

using DevInstance.LogScope;

public class OrderService
{
    private readonly IScopeLog log;

    public OrderService(IScopeManager logManager)
    {
        log = logManager.CreateLogger(this);
    }
}

Using Scopes

Wrap code sections in scopes using the using statement:

public void ProcessOrder(Order order)
{
    using var scope = log.DebugScope();

    // Your code here...
    scope.D("Order processed successfully");
}

Nested Scopes

Scopes can be nested to show hierarchical execution:

public void ProcessOrder(Order order)
{
    using var scope = log.DebugScope();

    ValidateOrder(order);
    CalculateTotal(order);
    SaveOrder(order);
}

private void ValidateOrder(Order order)
{
    using var scope = log.DebugScope();

    scope.D($"Validating order {order.Id}");
    // Validation logic...
}

private void CalculateTotal(Order order)
{
    using var scope = log.DebugScope();

    scope.D($"Calculating total for {order.Items.Count} items");
    // Calculation logic...
}

private void SaveOrder(Order order)
{
    using var scope = log.DebugScope();

    scope.D("Saving to database");
    // Save logic...
}

Log Levels

LogScope supports five log levels, from least to most verbose:

Level Description Use Case
NOLOG Logging disabled Production with no logging
ERROR Error messages only Production environments
WARNING Warnings and errors Production with diagnostics
INFO Informational messages General monitoring
DEBUG All messages Development and debugging

Setting Log Level

// At startup
services.AddConsoleScopeLogging(LogLevel.DEBUG);

// Or for console apps
var manager = DefaultScopeLogFactory.CreateConsoleLogger(LogLevel.INFO);

Scope Methods

Creating Scopes

Method Level Description
DebugScope() DEBUG Creates a debug-level scope
InfoScope() INFO Creates an info-level scope
WarningScope() WARNING Creates a warning-level scope
ErrorScope() ERROR Creates an error-level scope

Logging Within Scopes

Method Level Description
scope.D(message) DEBUG Log debug message
scope.I(message) INFO Log info message
scope.W(message) WARNING Log warning message
scope.E(message) ERROR Log error message
scope.E(exception) ERROR Log exception

Example

public async Task<Order> GetOrderAsync(string id)
{
    using var scope = log.DebugScope();

    scope.D($"Fetching order {id}");

    var order = await _repository.FindAsync(id);

    if (order == null)
    {
        scope.W($"Order {id} not found");
        return null;
    }

    scope.I($"Found order with {order.Items.Count} items");
    return order;
}

Configuration Options

DefaultFormattersOptions

Configure the output format when using Microsoft.Extensions.Logging integration:

services.AddMicrosoftScopeLogging(new DefaultFormattersOptions
{
    ShowTimestamp = true,      // Show timestamp in output
    ShowThreadNumber = true,   // Show thread ID
    ShowClassName = true,      // Show class name
    ShowMethodName = true,     // Show method name
    IndentSize = 2             // Indentation for nested scopes
});

Custom Formatters

You can create custom formatters by implementing IScopeFormatter:

public class CustomFormatter : IScopeFormatter
{
    public string FormatEntry(ScopeContext context)
    {
        return $"[START] {context.ClassName}.{context.MethodName}";
    }

    public string FormatExit(ScopeContext context, TimeSpan elapsed)
    {
        return $"[END] {context.ClassName}.{context.MethodName} ({elapsed.TotalMilliseconds}ms)";
    }

    public string FormatMessage(ScopeContext context, string message, LogLevel level)
    {
        return $"[{level}] {message}";
    }
}

Integration Examples

ASP.NET Core Web API

// Program.cs
using DevInstance.LogScope;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddConsoleScopeLogging(LogLevel.DEBUG);

var app = builder.Build();
app.MapControllers();
app.Run();
// OrdersController.cs
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IScopeLog log;
    private readonly IOrderService _orderService;

    public OrdersController(IScopeManager logManager, IOrderService orderService)
    {
        log = logManager.CreateLogger(this);
        _orderService = orderService;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Order>> GetOrder(string id)
    {
        using var scope = log.DebugScope();

        scope.D($"API request for order {id}");

        var order = await _orderService.GetByIdAsync(id);

        if (order == null)
        {
            scope.W("Order not found");
            return NotFound();
        }

        return Ok(order);
    }
}

Microsoft.Extensions.Logging Integration

using DevInstance.LogScope.Extensions.MicrosoftLogger;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMicrosoftScopeLogging(new DefaultFormattersOptions
{
    ShowTimestamp = true,
    ShowThreadNumber = true
});

// Your existing ILogger instances will work alongside LogScope
builder.Logging.AddConsole();

Console Application

using DevInstance.LogScope;

class Program
{
    static void Main(string[] args)
    {
        var manager = DefaultScopeLogFactory.CreateConsoleLogger(LogLevel.DEBUG);

        var processor = new DataProcessor(manager);
        processor.ProcessData();
    }
}

class DataProcessor
{
    private readonly IScopeLog log;

    public DataProcessor(IScopeManager manager)
    {
        log = manager.CreateLogger(this);
    }

    public void ProcessData()
    {
        using var scope = log.DebugScope();

        scope.I("Starting data processing");

        for (int i = 0; i < 10; i++)
        {
            ProcessItem(i);
        }

        scope.I("Data processing complete");
    }

    private void ProcessItem(int index)
    {
        using var scope = log.DebugScope();
        scope.D($"Processing item {index}");
        Thread.Sleep(100); // Simulate work
    }
}

Output Format

LogScope produces hierarchical output that clearly shows execution flow:

[12:34:56.789] --> OrdersController.GetOrder
[12:34:56.790]     API request for order ORD-123
[12:34:56.791]     --> OrderService.GetByIdAsync
[12:34:56.792]         Fetching order from database
[12:34:56.850]         --> OrderRepository.FindAsync
[12:34:56.920]         <-- OrderRepository.FindAsync (70ms)
[12:34:56.921]         Order found with 3 items
[12:34:56.922]     <-- OrderService.GetByIdAsync (131ms)
[12:34:56.923] <-- OrdersController.GetOrder (134ms)

Output Elements

Symbol Meaning
--> Scope entry (method start)
<-- Scope exit (method end) with elapsed time
Indentation Nesting level of scopes
(XXms) Execution time for the scope

Complete Examples

Full Service Implementation

using DevInstance.LogScope;

public interface IOrderService
{
    Task<Order> CreateOrderAsync(CreateOrderRequest request);
    Task<Order> GetByIdAsync(string id);
    Task<ModelList<Order>> GetOrdersAsync(OrderQuery query);
}

[WebService]
public class OrderService : IOrderService
{
    private readonly IScopeLog log;
    private readonly IOrderRepository _repository;
    private readonly IInventoryService _inventory;

    public OrderService(
        IScopeManager logManager,
        IOrderRepository repository,
        IInventoryService inventory)
    {
        log = logManager.CreateLogger(this);
        _repository = repository;
        _inventory = inventory;
    }

    public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
    {
        using var scope = log.DebugScope();

        scope.I($"Creating order for customer {request.CustomerId}");

        // Validate request
        ValidateRequest(request);

        // Check inventory
        await CheckInventoryAsync(request.Items);

        // Create order
        var order = new Order
        {
            Id = Guid.NewGuid().ToString(),
            CustomerId = request.CustomerId,
            Items = request.Items,
            CreatedAt = DateTime.UtcNow
        };

        // Calculate totals
        CalculateTotals(order);

        // Save to database
        await _repository.CreateAsync(order);

        scope.I($"Order {order.Id} created successfully");
        return order;
    }

    public async Task<Order> GetByIdAsync(string id)
    {
        using var scope = log.DebugScope();

        scope.D($"Fetching order {id}");

        var order = await _repository.FindAsync(id);

        if (order == null)
        {
            scope.W($"Order {id} not found");
        }

        return order;
    }

    public async Task<ModelList<Order>> GetOrdersAsync(OrderQuery query)
    {
        using var scope = log.DebugScope();

        scope.D($"Fetching orders: Page={query.Page}, PageSize={query.PageSize}");

        return await _repository.GetPagedAsync(query);
    }

    private void ValidateRequest(CreateOrderRequest request)
    {
        using var scope = log.DebugScope();

        if (string.IsNullOrEmpty(request.CustomerId))
        {
            scope.E("Customer ID is required");
            throw new BadRequestException("Customer ID is required");
        }

        if (request.Items == null || request.Items.Count == 0)
        {
            scope.E("Order must contain at least one item");
            throw new BadRequestException("Order must contain at least one item");
        }

        scope.D("Request validation passed");
    }

    private async Task CheckInventoryAsync(List<OrderItem> items)
    {
        using var scope = log.DebugScope();

        foreach (var item in items)
        {
            scope.D($"Checking inventory for product {item.ProductId}");

            var available = await _inventory.CheckAvailabilityAsync(
                item.ProductId,
                item.Quantity);

            if (!available)
            {
                scope.E($"Insufficient inventory for product {item.ProductId}");
                throw new BadRequestException(
                    $"Insufficient inventory for product {item.ProductId}");
            }
        }

        scope.I("All items available in inventory");
    }

    private void CalculateTotals(Order order)
    {
        using var scope = log.DebugScope();

        order.Subtotal = order.Items.Sum(i => i.Price * i.Quantity);
        order.Tax = order.Subtotal * 0.1m;
        order.Total = order.Subtotal + order.Tax;

        scope.D($"Order total: {order.Total:C}");
    }
}

API Reference

Core Interfaces

Interface Description
IScopeManager Factory for creating loggers
IScopeLog Logger instance for a class
ILogScope Active scope with logging methods
IScopeFormatter Custom output formatting

Factory Methods

Method Description
DefaultScopeLogFactory.CreateConsoleLogger(level) Create console logger
services.AddConsoleScopeLogging(level) Add DI console logging
services.AddMicrosoftScopeLogging(options) Add Microsoft.Extensions.Logging integration

IScopeManager Methods

Method Description
CreateLogger(object instance) Create logger for class instance
CreateLogger<T>() Create logger for type T
CreateLogger(string name) Create logger with custom name

IScopeLog Methods

Method Description
DebugScope() Create DEBUG level scope
InfoScope() Create INFO level scope
WarningScope() Create WARNING level scope
ErrorScope() Create ERROR level scope

ILogScope Methods

Method Description
D(string message) Log DEBUG message
I(string message) Log INFO message
W(string message) Log WARNING message
E(string message) Log ERROR message
E(Exception ex) Log exception