Auf dem Weg zur Herstellung der Umami-Produktion (Deutsch (German))

Auf dem Weg zur Herstellung der Umami-Produktion

Thursday, 20 November 2025

//

10 minute read

Einleitung

Als ich zum ersten Mal integriert Umami-Analysen in meine Blog-Plattform Ich bin schnell in eine frustrierende Realität gerannt - : Umami , ' , API-Dokumentation ist ,... , let , ' sein karitatives und nennen es ," ,minimal .." , Fehlermeldungen sind kryptisch bestenfalls , nicht , - , im schlimmsten Fall existent , und Parameter ändern zwischen Versionen ohne Warnung ,. , Und don ,mesK11 , auch erhalten Sie mich auf der ,-" , beep boop , E" , Bot Detection Response .

Also baute ich Umami-.-NET Nicht nur als einfacher HTTP-Wrapper, sondern auch als eine bereits fertige Clientbibliothek, die alle Umami kompensiert.'s quirks, die sich auf drei kritische Bereiche konzentrierten.

  1. Umfassende Validierung mit hilfreichen Fehlermeldungen
  2. Robuste Prüfinfrastruktur
  3. Anmutige Fehlerbehebung für reale --Szenarien

Lassen Sie mich gehen Sie durch, was macht diese Bibliothek Produktion...

NuGet Lizenz des MIT NIEDERLANDE

Das Problem der Dokumentationslücke von Umami'

Hier-' , was you-MSC1 , gegen die Arbeit mit Umami , ' , API direkt ,: .

  • Keine Eingangsvalidierung Senden Sie einen missgebildeten GUID, den Silent-Fehler
  • Kryptische Reaktionen Sie erhalten "beep boop"., das es ', das sie .
  • Durchbrechen von Änderungen Parameter, die zwischen den Versionen umbenannt werdenpath vs. url, hostname vs. host)
  • Zeitstempelverwirrtheit Viel Glück dabei, es herauszubekommen
  • JWT-Antwort Manchmal volle Nutzlasten, manchmal nur eine Besucher-ID, keine Dokumentation, die erklärt, wann oder warum.

Dies ist in Ordnung für einen schnellen Prototypen... aber für die Produktion... du brauchst etwas Besseres.

Guard Clauses®:-Fail Schnell mit Kontext

Die schlimmsten Fehler sind diejenigen, die stillschweigend fehlschlagen..-Umami-.-NET fängt Konfigurationsfehler beim Start, bevor sie Probleme in der Produktion verursachen können.

Konfigurationsvalidierung

public static void ValidateSettings(UmamiClientSettings settings)
{
    // Guard: UmamiPath is required
    if (string.IsNullOrEmpty(settings.UmamiPath))
        throw new ArgumentNullException(settings.UmamiPath,
            "UmamiUrl is required");

    // Guard: UmamiPath must be valid URI
    if (!Uri.TryCreate(settings.UmamiPath, UriKind.Absolute, out _))
        throw new FormatException(
            "UmamiUrl must be a valid Uri");

    // Guard: WebsiteId is required
    if (string.IsNullOrEmpty(settings.WebsiteId))
        throw new ArgumentNullException(settings.WebsiteId,
            "WebsiteId is required");

    // Guard: WebsiteId must be valid GUID
    if (!Guid.TryParseExact(settings.WebsiteId, "D", out _))
        throw new FormatException(
            "WebSiteId must be a valid Guid");
}

Dies läuft beim Start in Ihrem Program.csWenn Ihre Konfiguration falsch ist, wissen Sie sofort, wann das erste Analytics-Ereignis zu senden versucht.

Validierung mit hilfreichen Vorschlägen anfordern

Aber die wirkliche Magie ist in der Abfrage String-Hilfsperson... ..

public static string ToQueryString(this object obj)
{
    if (obj == null)
    {
        throw new ArgumentNullException(nameof(obj),
            "Cannot convert null object to query string. " +
            "Suggestion: Ensure you create and populate a request object " +
            "before calling ToQueryString().");
    }

    foreach (var property in objectType.GetProperties())
    {
        if (attribute.IsRequired)
        {
            if (propertyValue == null)
            {
                throw new ArgumentException(
                    $"Required parameter '{propertyName}' " +
                    $"(property '{property.Name}') cannot be null. " +
                    $"Suggestion: Set the {property.Name} property " +
                    $"on your {objectType.Name} object...",
                    property.Name);
            }

            // For strings, check for empty/whitespace
            if (propertyValue is string strValue &&
                string.IsNullOrWhiteSpace(strValue))
            {
                throw new ArgumentException(
                    $"Required parameter '{propertyName}' " +
                    $"cannot be empty or whitespace. " +
                    $"Suggestion: Set {property.Name} to a valid non-empty value.",
                    property.Name);
            }
        }
    }
}

Beachten Sie die Suggestion: Jede Fehlermeldung sagt Ihnen: was schief gelaufen ist und wie man es repariertDies ist die Dokumentation, die Umami zur Verfügung stellen sollte.

Datumsbereichsvalidierung

Beim Erstellen von Analyseanfragen können die Date Ranges knifflig sein.Die Bibliothek fängt diese Fehler für Sie ein.

public DateTime StartAtDate
{
    get => _startAtDate;
    set
    {
        if (_endAtDate != default && value > _endAtDate)
        {
            throw new ArgumentException(
                $"StartAtDate ({value:O}) must be before EndAtDate ({_endAtDate:O}). " +
                "Suggestion: Set StartAtDate to an earlier date or adjust EndAtDate.",
                nameof(StartAtDate));
        }
        _startAtDate = value;
    }
}

public virtual void Validate()
{
    if (StartAtDate == default)
    {
        throw new InvalidOperationException(
            "StartAtDate is required. " +
            "Suggestion: Set StartAtDate to a valid date " +
            "(e.g., DateTime.UtcNow.AddDays(-7) for last 7 days).");
    }
}

Umgang mit Umami-'-Quirks

Das Problem mit dem Beep Boop

Umami-'s Bot-Detektion gibt eine einfache Textantwort zurück "beep boop"Nicht JSON-., nicht ein richtiger Statuscode

Hier ist ', wie Umami®. NET es behandelt

public async Task<UmamiDataResponse> DecodeResponse(HttpResponseMessage response)
{
    var responseString = await response.Content.ReadAsStringAsync();

    // Handle bot detection
    if (responseString.Contains("beep") && responseString.Contains("boop"))
    {
        logger.LogWarning("Bot detected - data not stored in Umami");
        return new UmamiDataResponse(ResponseStatus.BotDetected);
    }

    // Handle JWT response
    try
    {
        var jwtPayload = DecodeJwt(responseString);
        return new UmamiDataResponse(ResponseStatus.Success, jwtPayload);
    }
    catch (Exception e)
    {
        logger.LogError(e, "Failed to decode response");
        return new UmamiDataResponse(ResponseStatus.Failed);
    }
}

Ihr Code bekommt ein sauberes Enum

public enum ResponseStatus
{
    Failed,
    BotDetected,
    Success
}

Keine seltsamen Antworten mehr parsentieren. Überprüfen Sie einfach den Status.

Parameter-Namensänderungen

Umami umbenannte Parameter zwischen API-Versionen-.. Haben sie diese dokumentiert??. Natürlich nicht... Die Bibliothek behandelt beides.

// Support both old and new parameter names
request.Path = queryParams["path"] ?? queryParams["url"];
request.Hostname = queryParams["hostname"] ?? queryParams["host"];

Zeitstempelkonversionen

Umami verwendet Unix Millisekunden für Zeitstempel... Hier ist ein Helfer, der es schmerzlos macht.

public static long ToMilliseconds(this DateTime dateTime)
{
    var dateTimeOffset = new DateTimeOffset(dateTime.ToUniversalTime());
    return dateTimeOffset.ToUnixTimeMilliseconds();
}

Jetzt können Sie mit normalen arbeiten DateTime Objekte und lassen Sie die Bibliothek mit der Konvertierung umgehen

Hintergrundbearbeitung mit Kanälen

Analytics sollte nie blockieren Sie Ihre Anwendung-.-Umami-.-NET enthält einen Hintergrund Sender mit System.Threading.Channels:

public class UmamiBackgroundSender : IHostedService
{
    private readonly Channel<UmamiPayload> _channel;
    private readonly UmamiClient _client;

    public async Task Track(string eventName,
        string? url = null,
        UmamiEventData? data = null)
    {
        var payload = new UmamiPayload
        {
            Website = _settings.WebsiteId,
            Name = eventName,
            Url = url ?? string.Empty,
            Data = data
        };

        // Non-blocking write to channel
        await _channel.Writer.WriteAsync(payload);
    }

    private async Task ProcessQueue(CancellationToken stoppingToken)
    {
        await foreach (var payload in _channel.Reader.ReadAllAsync(stoppingToken))
        {
            try
            {
                await _client.Send(payload);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to send event to Umami");
            }
        }
    }
}

Ereignisse werden im Speicher abgewartet und asynchron verarbeitet..-Ihre Web Requests kehren sofort zurück.,-Analytics geschehen im Hintergrund.

Retry Politik mit Polly

Netzwerkausfälle passieren, .-Die Bibliothek verwendet Polly für belastbare HTTP-Aufrufe.:-

public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    var delay = Backoff.DecorrelatedJitterBackoffV2(
        TimeSpan.FromSeconds(1),
        retryCount: 3);

    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode == HttpStatusCode.ServiceUnavailable)
        .WaitAndRetryAsync(delay);
}

Vorübergehende Ausfälle und Fehler bei der Fehlerbehebung lösen automatische Retries mit exponentiellem Backoff aus...Ihre Analysen sind widerstandsfähig gegen temporäre Netzwerkprobleme.MSC2.

Authentisierung mit Auto-MSA-Erneuerung

Beim Abrufen von Analysedaten nicht nur Ereignisse senden, benötigen Sie Authentifizierung. Die Bibliothek verarbeitet Token-Ablauf automatisch.

public async Task<UmamiResult<StatsResponseModel>> GetStats(StatsRequest statsRequest)
{
    var response = await _httpClient.GetAsync(url);

    // Token expired? Re-authenticate and retry
    if (response.StatusCode == HttpStatusCode.Unauthorized)
    {
        await _authService.Login();
        return await GetStats(statsRequest); // Recursive retry
    }

    // Parse and return
    var content = await response.Content.ReadFromJsonAsync<StatsResponseModel>();
    return new UmamiResult<StatsResponseModel>(
        response.StatusCode,
        response.ReasonPhrase ?? string.Empty,
        content);
}

Sie müssen nie über Token-Management denken

Prüfinfrastruktur

Produktion ?-?ready-Code braucht umfassende Tests ?.?Hier ist, was ich gebaut habe ?mesK3?

FakeLogger für Protokollverifizierung

Verwendung von Microsoft-' FakeLogger Paket Tests können das Logging-Verhalten überprüfen

[Fact]
public async Task Login_Success_LogsMessage()
{
    // Arrange
    var fakeLogger = new FakeLogger<AuthService>();
    var authService = new AuthService(httpClient, settings, fakeLogger);

    // Act
    await authService.Login();

    // Assert
    var logs = fakeLogger.Collector.GetSnapshot();
    Assert.Contains("Login successful", logs.Select(x => x.Message));
}

Benutzerdefinierte Mock HTTP-Handler

Das Testen von async Operationen ist knifflig. TaskCompletionSource:

[Fact]
public async Task BackgroundSender_ProcessesEventAsynchronously()
{
    var tcs = new TaskCompletionSource<bool>();

    var handler = EchoMockHandler.Create(async (message, token) =>
    {
        try
        {
            // Assert the request was sent correctly
            var payload = await message.Content.ReadFromJsonAsync<UmamiPayload>();
            Assert.Equal("test-event", payload.Name);

            tcs.SetResult(true); // Signal test completion
            return new HttpResponseMessage(HttpStatusCode.OK);
        }
        catch (Exception e)
        {
            tcs.SetException(e);
            return new HttpResponseMessage(HttpStatusCode.InternalServerError);
        }
    });

    // Track event
    await backgroundSender.Track("test-event");

    // Wait for background processing with timeout
    var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(1000));
    if (completedTask != tcs.Task)
        throw new TimeoutException("Event was not processed within timeout");

    await tcs.Task; // Throw if assertions failed
}

Dieses Muster sorgt dafür, dass

  • Hintergrundereignisse werden tatsächlich verarbeitet
  • Die Verarbeitung erfolgt innerhalb einer angemessenen Frist
  • Assertionen in der Mock Handler werden ordnungsgemäß berichtet

Umfassende Testabdeckung

Die Testsuite umfasst:

  • Konfigurations-Validierung für nicht gültige GUIDs für fehlende URLs für )
  • Ereignisverfolgung mit und ohne Daten
  • Verfolgung der Seitenansicht
  • Benutzerkennung
  • Handhabung der Bot-Detektion
  • JWT-Antwortdekodierung
  • Hintergrundbearbeitung mit Timeouts
  • Datumsbereichsvalidierung
  • Suchzeichenfolge-Generierung
  • Authentifizierung und Token-Refresh
  • Metrik und Seitenansichten Datenwiederherstellung

Reale Verwendung von -

Hier ist, wie einfach es ist, in einer ASP...NET Core Anwendung zu verwenden.

Einrichten im Programm

builder.Services.SetupUmamiClient(builder.Configuration);

Die Bibliothek liest Ihre appsettings.json:

{
  "Analytics": {
    "UmamiPath": "https://analytics.yoursite.com",
    "WebsiteId": "your-website-guid"
  }
}

Ereignisse verfolgen

public class HomeController : Controller
{
    private readonly UmamiBackgroundSender _umami;

    public HomeController(UmamiBackgroundSender umami)
    {
        _umami = umami;
    }

    public IActionResult Index()
    {
        // Non-blocking event tracking
        await _umami.TrackPageView("/", "Home Page");

        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Subscribe(string email)
    {
        // Track with custom data
        await _umami.Track("newsletter-signup",
            data: new UmamiEventData
            {
                { "source", "homepage" },
                { "email_domain", email.Split('@')[1] }
            });

        return RedirectToAction("ThankYou");
    }
}

Daten für die Analyse abrufen

public class AnalyticsDashboardController : Controller
{
    private readonly IUmamiDataService _umamiData;

    public async Task<IActionResult> Stats()
    {
        var request = new StatsRequest
        {
            StartAtDate = DateTime.UtcNow.AddDays(-30),
            EndAtDate = DateTime.UtcNow
        };

        var result = await _umamiData.GetStats(request);

        if (result.Status == HttpStatusCode.OK)
        {
            var stats = result.Data;
            // stats.Visitors, stats.PageViews, stats.BounceRate, etc.
            return View(stats);
        }

        return View("Error");
    }
}

Was ist das nächste für ,MSK'?

Die Bibliothek ist feature.-.complete and battle...-.betestet in der Produktion auf diesem Blog... vor dem Release von 1.0.

  • Umfassende API-Dokumentation
  • Veröffentlichen des Pakets NuGet
  • Leistungsvergleiche
  • Weitere Komfort-Methoden für häufige Analyseabfragen

Schlußfolgerung

Eine Produktion aufbauen – -bereite Bibliothek ist es, eine API zu packen.'s über das Erstellen einer Erfahrung, die 's besser als die Verwendung der API direkt ,. , Umami , . ,NET kompensiert umami .' , Dokumentationslücken mit ,: ,

  • Validierung, die erklärt, was schief gelaufen ist und wie man es repariert
  • Anmutiger Umgang mit skurrilen API-Verhalten
  • Umfassende Prüfung, die beweist, dass es funktioniert
  • Hintergrundverarbeitung, die nicht blockiert Ihre App
  • Resiliente Fehlerbehandlung mit automatischen Retries

Wenn Sie mit Umami-Analytics in einer Anwendung von ..NET verwenden Umami-.-NET' ist eine offene Quelle, die stark getestet und entwickelt wurde, um Ihr Leben einfacher zu machen.

Haben Sie Fragen oder Anregungen?? Öffnen Sie ein Problem auf GitHub oder greifen Sie in den Kommentaren unten an

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.