Nachverfolgen von benutzerdefinierten Vorgängen mit dem Application Insights .NET SDK

Mit Application Insights SDKs werden eingehende HTTP-Anforderungen und Aufrufe abhängiger Dienste wie HTTP-Anforderungen und SQL-Abfragen automatisch nachverfolgt. Dank der Nachverfolgung und Korrelation von Anforderungen und Abhängigkeiten erhalten Sie Einblicke in die Reaktionsfähigkeit und Zuverlässigkeit der gesamten Anwendung für alle Microservices, die diese Anwendung vereint.

Es gibt eine Klasse von Anwendungsmustern, die nicht generisch unterstützt werden kann. Für die richtige Überwachung dieser Muster ist eine manuelle Codeinstrumentierung erforderlich. In diesem Artikel werden einige Muster behandelt, die möglicherweise manuelle Instrumentierung erfordern, darunter benutzerdefinierte Warteschlangenverarbeitung und das Ausführen von zeitintensiven Hintergrundaufgaben.

Dieser Artikel enthält eine Anleitung zum Nachverfolgen von benutzerdefinierten Vorgängen mit dem Application Insights SDK. Diese Dokumentation ist relevant für:

  • Application Insights für .NET (auch als Basis SDK bezeichnet) ab Version 2.4.
  • Application Insights für Webanwendungen (mit Ausführung von ASP.NET) ab Version 2.4.
  • Application Insights ab ASP.NET Core Version 2.1.

Hinweis

Die folgende Dokumentation basiert auf der klassischen Application Insights-API. Der langfristige Plan für Application Insights besteht darin, Daten mithilfe von OpenTelemetry zu sammeln. Weitere Informationen finden Sie unter Aktivieren von Azure Monitor OpenTelemetry für .NET-, Node.js-, Python- und Java-Anwendungen.

Überblick

Ein Vorgang ist ein logisches Stück Arbeit, das von einer Anwendung ausgeführt wird. Er verfügt über Name, Startzeit, Dauer, Ergebnis und Ausführungskontext, z.B. Benutzername, Eigenschaften und Ergebnis. Wenn Vorgang A von Vorgang B initiiert wurde, dann ist Vorgang B ein übergeordneter Vorgang von A. Ein Vorgang kann nur über einen übergeordneten Vorgang verfügen, aber über mehrere untergeordnete Vorgänge. Weitere Informationen zu Vorgängen und zur Telemetriekorrelation finden Sie unter Korrelation der Application Insights-Telemetrie.

Im Application Insights .NET SDK wird ein Vorgang mit der abstrakten OperationTelemetry-Klasse und ihren Nachfolgerelementen RequestTelemetry und DependencyTelemetry beschrieben.

Nachverfolgen von eingehenden Vorgängen

Das Application Insights-Web SDK sammelt automatisch HTTP-Anforderungen für ASP.NET-Anwendungen, die in einer IIS-Pipeline und allen ASP.NET Core-Anwendungen ausgeführt werden. Es sind auch Lösungen mit Community-Support für andere Plattformen und Frameworks vorhanden. Falls die Anwendung von keiner Standardlösung oder Lösung mit Community-Support unterstützt wird, können Sie sie manuell instrumentieren.

Ein weiteres Beispiel, für das die benutzerdefinierte Nachverfolgung erforderlich ist, ist der Worker, der Elemente aus der Warteschlange erhält. Für einige Warteschlangen wird der Aufruf zum Hinzufügen einer Nachricht zur Warteschlange als Abhängigkeit nachverfolgt. Der allgemeine Vorgang, der die Nachrichtenverarbeitung beschreibt, wird nicht automatisch erfasst.

Wir sehen uns nun an, wie Vorgänge dieser Art nachverfolgt werden können.

Auf allgemeiner Ebene besteht die Aufgabe darin, RequestTelemetry zu erstellen und bekannte Eigenschaften festzulegen. Nachdem der Vorgang abgeschlossen ist, wird die Telemetrie nachverfolgt. Das folgende Beispiel veranschaulicht diese Aufgabe.

HTTP-Anforderung in selbstgehosteter Owin-App

In diesem Beispiel wird der Kontext der Ablaufverfolgung gemäß dem HTTP-Protokoll für die Korrelation verteilt. Sie sollten davon ausgehen, die hier beschriebenen Header zu erhalten.

public class ApplicationInsightsMiddleware : OwinMiddleware
{
    // You may create a new TelemetryConfiguration instance, reuse one you already have,
    // or fetch the instance created by Application Insights SDK.
    private readonly TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.CreateDefault();
    private readonly TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration);
    
    public ApplicationInsightsMiddleware(OwinMiddleware next) : base(next) {}

    public override async Task Invoke(IOwinContext context)
    {
        // Let's create and start RequestTelemetry.
        var requestTelemetry = new RequestTelemetry
        {
            Name = $"{context.Request.Method} {context.Request.Uri.GetLeftPart(UriPartial.Path)}"
        };

        // If there is a Request-Id received from the upstream service, set the telemetry context accordingly.
        if (context.Request.Headers.ContainsKey("Request-Id"))
        {
            var requestId = context.Request.Headers.Get("Request-Id");
            // Get the operation ID from the Request-Id (if you follow the HTTP Protocol for Correlation).
            requestTelemetry.Context.Operation.Id = GetOperationId(requestId);
            requestTelemetry.Context.Operation.ParentId = requestId;
        }

        // StartOperation is a helper method that allows correlation of 
        // current operations with nested operations/telemetry
        // and initializes start time and duration on telemetry items.
        var operation = telemetryClient.StartOperation(requestTelemetry);

        // Process the request.
        try
        {
            await Next.Invoke(context);
        }
        catch (Exception e)
        {
            requestTelemetry.Success = false;
            requestTelemetry.ResponseCode;
            telemetryClient.TrackException(e);
            throw;
        }
        finally
        {
            // Update status code and success as appropriate.
            if (context.Response != null)
            {
                requestTelemetry.ResponseCode = context.Response.StatusCode.ToString();
                requestTelemetry.Success = context.Response.StatusCode >= 200 && context.Response.StatusCode <= 299;
            }
            else
            {
                requestTelemetry.Success = false;
            }

            // Now it's time to stop the operation (and track telemetry).
            telemetryClient.StopOperation(operation);
        }
    }
    
    public static string GetOperationId(string id)
    {
        // Returns the root ID from the '|' to the first '.' if any.
        int rootEnd = id.IndexOf('.');
        if (rootEnd < 0)
            rootEnd = id.Length;

        int rootStart = id[0] == '|' ? 1 : 0;
        return id.Substring(rootStart, rootEnd - rootStart);
    }
}

Das HTTP-Protokoll für die Korrelation deklariert außerdem den Correlation-Context-Header. Er wird hier zur Vereinfachung weggelassen.

Warteschlangeninstrumentierung

Der W3C Trace Context und ein HTTP-Protokoll für die Korrelation übergeben Korrelationsdetails mit HTTP-Anforderungen, aber für jedes Warteschlangenprotokoll muss definiert werden, wie die gleichen Details für die Warteschlangennachricht übergeben werden. Einige Warteschlangenprotokolle, z. B. AMQP, ermöglichen das Übergeben weiterer Metadaten. Andere Protokolle, z. B. die Azure Storage-Warteschlange, setzen voraus, dass der Kontext in der Nachrichtennutzlast codiert wird.

Hinweis

Die komponentenübergreifende Ablaufverfolgung wird für Warteschlangen noch nicht unterstützt.

Wenn mit HTTP Ihr Producer und Consumer Telemetriedaten an verschiedene Application Insights-Ressourcen senden, zeigen die Oberfläche zur Transaktionsdiagnose und die Anwendungsübersicht Transaktionen und Übersicht End-to-End an. Bei Warteschlangen wird diese Funktion noch nicht unterstützt.

Service Bus-Warteschlange

Informationen zur Ablaufverfolgung finden Sie unter Verteilte Ablaufverfolgung und Korrelation über Service Bus-Messaging.

Azure Storage-Warteschlange

Im folgenden Beispiel wird veranschaulicht, wie Sie Vorgänge der Azure Storage-Warteschlange nachverfolgen und die Telemetrie zwischen Producer, Consumer und Azure Storage korrelieren.

Die Storage-Warteschlange verfügt über eine HTTP-API. Alle Aufrufe der Warteschlange werden von der Application Insights-Abhängigkeitserfassung für HTTP-Anforderungen nachverfolgt. Sie ist standardmäßig für ASP.NET- und ASP.NET Core-Anwendungen konfiguriert. Weitere Arten von Anwendungen finden Sie in der Dokumentation zu Konsolenanwendungen.

Zudem sollten Sie die Vorgangs-ID von Application Insights mit der Anforderungs-ID von Storage korrelieren. Informationen zum Festlegen und Abrufen eines Storage-Anforderungsclients und einer Serveranforderungs-ID erhalten Sie unter Überwachung, Diagnose und Problembehandlung in Azure Storage.

Einreihen in die Warteschlange

Da Storage-Warteschlangen die HTTP-API unterstützen, werden alle Vorgänge der Warteschlange von Application Insights automatisch nachverfolgt. In vielen Fällen sollte diese Instrumentierung ausreichend sein. Zum Korrelieren von Ablaufverfolgungen auf Consumerseite mit Ablaufverfolgungen für Producer müssen Sie auf ähnliche Weise Korrelationskontext übergeben, wie wir dies für das HTTP-Protokoll für die Korrelation getan haben.

In diesem Beispiel wird gezeigt, wie Sie den Enqueue-Vorgang nachverfolgen. Ihre Möglichkeiten:

  • Neuversuche korrelieren (falls vorhanden) : Diese verfügen allesamt über ein gemeinsames übergeordnetes Element, den Enqueue-Vorgang. Andernfalls werden sie als untergeordnete Elemente der eingehenden Anforderung nachverfolgt. Wenn also mehrere logische Anforderungen für die Warteschlange vorhanden sind, lässt sich unter Umständen nur schwer ermitteln, welcher Aufruf zu den erneuten Versuchen geführt hat.
  • Speicherprotokolle korrelieren (falls erforderlich) : Sie sind mit der Telemetrie von Application Insights korreliert.

Der Enqueue-Vorgang ist das untergeordnete Element eines übergeordneten Vorgangs. Ein Beispiel ist eine eingehende HTTP-Anforderung. Der HTTP-Abhängigkeitsaufruf ist dem Enqueue-Vorgang untergeordnet und der eingehenden Anforderung auf zweiter Ebene untergeordnet.

public async Task Enqueue(CloudQueue queue, string message)
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("enqueue " + queue.Name);
    operation.Telemetry.Type = "Azure queue";
    operation.Telemetry.Data = "Enqueue " + queue.Name;

    // MessagePayload represents your custom message and also serializes correlation identifiers into payload.
    // For example, if you choose to pass payload serialized to JSON, it might look like
    // {'RootId' : 'some-id', 'ParentId' : '|some-id.1.2.3.', 'message' : 'your message to process'}
    var jsonPayload = JsonConvert.SerializeObject(new MessagePayload
    {
        RootId = operation.Telemetry.Context.Operation.Id,
        ParentId = operation.Telemetry.Id,
        Payload = message
    });
    
    CloudQueueMessage queueMessage = new CloudQueueMessage(jsonPayload);

    // Add operation.Telemetry.Id to the OperationContext to correlate Storage logs and Application Insights telemetry.
    OperationContext context = new OperationContext { ClientRequestID = operation.Telemetry.Id};

    try
    {
        await queue.AddMessageAsync(queueMessage, null, null, new QueueRequestOptions(), context);
    }
    catch (StorageException e)
    {
        operation.Telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
        operation.Telemetry.Success = false;
        operation.Telemetry.ResultCode = e.RequestInformation.HttpStatusCode.ToString();
        telemetryClient.TrackException(e);
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }
}  

Wenn Sie die Menge an Telemetriedaten, die von Ihrer Anwendung gemeldet werden, reduzieren oder den Enqueue-Vorgang aus anderen Gründen nachverfolgen möchten, können Sie die Activity-API direkt verwenden:

  • Erstellen (und starten) Sie eine neue Activity, anstatt den Application Insights-Vorgang zu starten. Sie müssen ihr dabei keine Eigenschaften zuweisen außer den Namen des Vorgangs.
  • Serialisieren Sie yourActivity.Id anstelle von operation.Telemetry.Id für die Nachrichtennutzlast. Sie können auch Activity.Current.Id verwenden.

Entfernen aus der Warteschlange

Ähnlich wie bei Enqueue werden die tatsächlichen HTTP-Anforderungen für die Storage-Warteschlange von Application Insights automatisch nachverfolgt. Der Enqueue-Vorgang wird normalerweise im Kontext des übergeordneten Elements durchgeführt, z.B. im Kontext einer eingehenden Anforderung. Die SDKs von Application Insights korrelieren Vorgänge dieser Art und den dazugehörigen HTTP-Teil automatisch mit der übergeordneten Anforderung und anderen Telemetriedaten, die für denselben Bereich gemeldet werden.

Der Dequeue-Vorgang ist nicht ganz einfach. Das Application Insights SDK verfolgt HTTP-Anforderungen automatisch. Allerdings kennt es den Korrelationskontext erst, wenn die Nachricht analysiert wird. Es ist nicht möglich, die HTTP-Anforderung zu korrelieren, um die Nachricht mit den restlichen Telemetriedaten zu erhalten, wenn mehrere Nachrichten empfangen werden.

public async Task<MessagePayload> Dequeue(CloudQueue queue)
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("dequeue " + queue.Name);
    operation.Telemetry.Type = "Azure queue";
    operation.Telemetry.Data = "Dequeue " + queue.Name;
    
    try
    {
        var message = await queue.GetMessageAsync();
    }
    catch (StorageException e)
    {
        operation.telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
        operation.telemetry.Success = false;
        operation.telemetry.ResultCode = e.RequestInformation.HttpStatusCode.ToString();
        telemetryClient.TrackException(e);
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }

    return null;
}

Prozess

Im folgenden Beispiel wird eine eingehende Nachricht auf ähnliche Weise wie eine eingehende HTTP-Anforderung nachverfolgt:

public async Task Process(MessagePayload message)
{
    // After the message is dequeued from the queue, create RequestTelemetry to track its processing.
    RequestTelemetry requestTelemetry = new RequestTelemetry { Name = "process " + queueName };
    
    // It might also make sense to get the name from the message.
    requestTelemetry.Context.Operation.Id = message.RootId;
    requestTelemetry.Context.Operation.ParentId = message.ParentId;

    var operation = telemetryClient.StartOperation(requestTelemetry);

    try
    {
        await ProcessMessage();
    }
    catch (Exception e)
    {
        telemetryClient.TrackException(e);
        throw;
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }
}

Auch andere Warteschlangenvorgänge können so instrumentiert werden. Ein Peekvorgang sollte ähnlich wie ein Vorgang zur Entfernung aus der Warteschlange instrumentiert werden. Das Instrumentieren von Vorgängen der Warteschlangenverwaltung ist nicht erforderlich. Mit Application Insights werden Vorgänge wie HTTP-Vorgänge nachverfolgt, und in den meisten Fällen ist dies ausreichend.

Stellen Sie beim Instrumentieren der Nachrichtenlöschung sicher, dass Sie die Vorgangsbezeichner (für die Korrelation) festlegen. Alternativ können Sie auch die Activity-API verwenden. Das Festlegen von Vorgangsbezeichnern für die Telemetrieelemente ist dann nicht erforderlich, da das Application Insights SDK diese Aufgabe für Sie übernimmt:

  • Erstellen Sie nach Erhalt eines Elements aus der Warteschlange eine neue Activity.
  • Verwenden Sie Activity.SetParentId(message.ParentId) zum Korrelieren von Consumer- und Producerprotokollen.
  • Starten Sie die Activity.
  • Verfolgen Sie Vorgänge zum Entfernen aus der Warteschlange, Verarbeiten und Löschen nach, indem Sie Start/StopOperation-Hilfsprogramme verwenden. Führen Sie dies über die gleiche asynchrone Ablaufsteuerung (Ausführungskontext) durch. Auf diese Weise werden sie richtig korreliert.
  • Beenden Sie die Activity.
  • Verwenden Sie Start/StopOperation, oder rufen Sie Track der Telemetrie manuell auf.

Abhängigkeitstypen

Application Insights verwendet Abhängigkeitstypen, um Benutzeroberflächen anzupassen. Für Warteschlangen werden folgende Typen von DependencyTelemetry erkannt, die die Oberfläche zur Transaktionsdiagnose verbessern:

  • Azure queue für Azure Storage-Warteschlangen
  • Azure Event Hubs für Azure Event Hubs
  • Azure Service Bus für Azure Service Bus

Batchverarbeitung

Bei einigen Warteschlangen können Sie mehrere Nachrichten mit einer Anforderung aus der Warteschlange entfernen. Die Verarbeitung solcher Nachrichten ist vermutlich unabhängig und gehört zu den verschiedenen logischen Vorgängen. Es ist nicht möglich, den Dequeue-Vorgang mit einer bestimmten Nachricht zu korrelieren, die verarbeitet wird.

Jede Nachricht sollte in einer eigenen asynchronen Ablaufsteuerung verarbeitet werden. Weitere Informationen erhalten Sie im Abschnitt Nachverfolgung von ausgehenden Abhängigkeiten.

Hintergrundaufgaben mit langer Ausführungsdauer

Einige Anwendungen starten einen Vorgang mit langer Ausführungsdauer, der unter Umständen durch Benutzeranforderungen verursacht wird. Aus Sicht der Nachverfolgung bzw. Instrumentierung unterscheidet sich dies nicht von der Instrumentierung von Anforderungen oder Abhängigkeiten:

async Task BackgroundTask()
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>(taskName);
    operation.Telemetry.Type = "Background";
    try
    {
        int progress = 0;
        while (progress < 100)
        {
            // Process the task.
            telemetryClient.TrackTrace($"done {progress++}%");
        }
        // Update status code and success as appropriate.
    }
    catch (Exception e)
    {
        telemetryClient.TrackException(e);
        // Update status code and success as appropriate.
        throw;
    }
    finally
    {
        telemetryClient.StopOperation(operation);
    }
}

In diesem Beispiel wird mit telemetryClient.StartOperation das Element DependencyTelemetry erstellt und der Korrelationskontext eingefügt. Angenommen, Sie verfügen über einen übergeordneten Vorgang, der von eingehenden Anforderungen für die Planung des Vorgangs erstellt wurde. Sofern BackgroundTask in derselben asynchronen Ablaufsteuerung wie eine eingehende Anforderung gestartet wird, wird die Korrelation mit diesem übergeordneten Vorgang durchgeführt. BackgroundTask und alle geschachtelten Telemetrieelemente werden automatisch mit der Anforderung korreliert, die der Auslöser war. Dies gilt auch nach Abschluss der Anforderung.

Wenn der Task über den Hintergrundthread gestartet wird, dem kein Vorgang (Activity) zugeordnet ist, weist BackgroundTask kein übergeordnetes Element auf. Geschachtelte Vorgänge können jedoch vorhanden sein. Alle Telemetrieelemente, die von dem Task gemeldet werden, werden mit dem in BackgroundTask erstellten DependencyTelemetry-Element korreliert.

Nachverfolgung von ausgehenden Abhängigkeiten

Sie können Ihre eigene Art von Abhängigkeit oder einen Vorgang nachverfolgen, der nicht von Application Insights unterstützt wird.

Die Enqueue-Methode in der Service Bus-Warteschlange oder Storage-Warteschlange kann als Beispiel für diese benutzerdefinierte Nachverfolgung dienen.

Der allgemeine Ansatz für die benutzerdefinierte Nachverfolgung von Abhängigkeiten lautet wie folgt:

  • Rufen Sie die TelemetryClient.StartOperation-Methode (Erweiterungsmethode) auf, mit der die für die Korrelation erforderlichen DependencyTelemetry-Eigenschaften und einige andere Eigenschaften wie Start, Zeitstempel und Dauer aufgefüllt werden.
  • Legen Sie weitere benutzerdefinierte Eigenschaften wie den Namen und anderen benötigten Kontext für DependencyTelemetry fest.
  • Rufen Sie eine Abhängigkeit auf und warten Sie darauf.
  • Beenden Sie den Vorgang mit StopOperation, wenn er abgeschlossen wurde.
  • Behandeln Sie Ausnahmen.
public async Task RunMyTaskAsync()
{
    using (var operation = telemetryClient.StartOperation<DependencyTelemetry>("task 1"))
    {
        try 
        {
            var myTask = await StartMyTaskAsync();
            // Update status code and success as appropriate.
        }
        catch(...) 
        {
            // Update status code and success as appropriate.
        }
    }
}

Das Beenden eines Vorgangs führt dazu, dass der Vorgang gestoppt wird, sodass Sie dies tun können, anstatt den Befehl StopOperation aufzurufen.

Warnung

In einigen Fällen kann eine nicht behandelte Ausnahme verhindern, dass finally aufgerufen wird, sodass Vorgänge möglicherweise nicht nachverfolgt werden.

Verarbeitung und Nachverfolgung von parallelen Vorgängen

Das Aufrufen von StopOperation beendet nur den Vorgang, der gestartet wurde. Wenn der aktuell ausgeführte Vorgang nicht mit dem zu stoppenden übereinstimmt, wird von StopOperation keine Aktion ausgeführt. Dies kann der Fall sein, wenn Sie mehrere Vorgänge parallel in demselben Ausführungskontext starten.

var firstOperation = telemetryClient.StartOperation<DependencyTelemetry>("task 1");
var firstTask = RunMyTaskAsync();

var secondOperation = telemetryClient.StartOperation<DependencyTelemetry>("task 2");
var secondTask = RunMyTaskAsync();

await firstTask;

// FAILURE!!! This will do nothing and will not report telemetry for the first operation
// as currently secondOperation is active.
telemetryClient.StopOperation(firstOperation); 

await secondTask;

Stellen Sie sicher, dass Sie immer StartOperation aufrufen und den Vorgang in derselben async-Methode verarbeiten, um parallel ausgeführte Vorgänge zu isolieren. Wenn der Vorgang synchron (bzw. nicht asynchron) ist, können Sie den Vorgang per Wrapper umschließen und mit Task.Run nachverfolgen.

public void RunMyTask(string name)
{
    using (var operation = telemetryClient.StartOperation<DependencyTelemetry>(name))
    {
        Process();
        // Update status code and success as appropriate.
    }
}

public async Task RunAllTasks()
{
    var task1 = Task.Run(() => RunMyTask("task 1"));
    var task2 = Task.Run(() => RunMyTask("task 2"));
    
    await Task.WhenAll(task1, task2);
}

Application Insights-Vorgänge im Vergleich zu System.Diagnostics.Activity

System.Diagnostics.Activity stellt den verteilten Ablaufverfolgungskontext dar und wird von Frameworks und Bibliotheken verwendet, um Kontext zu erstellen sowie innerhalb und außerhalb des Prozesses weiterzugeben und um Telemetrieelemente zu korrelieren. Activity arbeitet mit System.Diagnostics.DiagnosticSource zusammen, dem Benachrichtigungsmechanismus des Frameworks bzw. der Bibliothek zum Benachrichtigen bei interessanten Ereignissen wie eingehenden oder ausgehenden Anforderungen und Ausnahmen.

Aktivitäten sind erstklassige Citizens in Application Insights. Automatische Abhängigkeit und Anforderungssammlung basieren zusammen mit DiagnosticSource-Ereignissen stark auf ihnen. Wenn Sie eine Activity in Ihrer Anwendung erstellen, führt dies nicht zur Erstellung von Application Insights-Telemetrie. Application Insights muss DiagnosticSource-Ereignisse empfangen und die Namen und Nutzlasten der Ereignisse kennen, um eine Activity in Telemetrie zu übersetzen.

Jeder Application Insights-Vorgang (Anforderung oder Abhängigkeit) umfasst Activity. Wenn StartOperation aufgerufen wird, wird Activity darunter erstellt. StartOperation ist die empfohlene Methode, um Anforderungs- oder Abhängigkeitstelemetrien manuell nachzuverfolgen und sicherzustellen, dass alle Elemente korreliert sind.

Nächste Schritte