<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wesley Skeen</title>
    <description>The latest articles on DEV Community by Wesley Skeen (@wesley_skeen).</description>
    <link>https://dev.to/wesley_skeen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F976458%2F23a37298-e7b5-4545-8b63-dd1aa26f00bb.jpeg</url>
      <title>DEV Community: Wesley Skeen</title>
      <link>https://dev.to/wesley_skeen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wesley_skeen"/>
    <language>en</language>
    <item>
      <title>A workflow pattern in F#</title>
      <dc:creator>Wesley Skeen</dc:creator>
      <pubDate>Tue, 05 Dec 2023 09:37:09 +0000</pubDate>
      <link>https://dev.to/wesley_skeen/a-workflow-pattern-in-f-1hci</link>
      <guid>https://dev.to/wesley_skeen/a-workflow-pattern-in-f-1hci</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/WesleySkeen"&gt;
        WesleySkeen
      &lt;/a&gt; / &lt;a href="https://github.com/WesleySkeen/fsharp-workflow-patterns"&gt;
        fsharp-workflow-patterns
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;During a lift, refactor, shift (C# -&amp;gt; F#), I came across a feature in our codebase that was a good candidate for a workflow pattern. It contained the following steps and was fairly procedural in design. Basically the code done some validation and if the validation was successful we would call some external service. Each service had its own steps for validation. There was a lot of mutation and which resulted in huge side effects that made the code hard to debug and read. The layout was something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var canCallServiceA = false
var canCallServiceB = false
var canCallServiceC = false

if email is poulated and phone number is populated
    canCallServiceA = true
else
    canCallServiceA = false

if phone number is populated and canCallServiceA = false
    canCallServiceB = true
else
    canCallServiceB = false

if phone number is not populated and canCallServiceB = true
    canCallServiceC = false
else
    canCallServiceC = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a really simplistic version of what is actually going on in the code base.&lt;/p&gt;

&lt;p&gt;Since we didn't want to straight up migrate and inherit the design of the current implementation, we came up with a pattern to convert the logic into a clean workflow pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seq functions
&lt;/h2&gt;

&lt;p&gt;We heavily relied on Seq functions to get this one across the line, in particular &lt;a href="https://github.com/dotnet/fsharp/blob/main/src/FSharp.Core/seq.fs#L906-906"&gt;Seq.Fold&lt;/a&gt;. This allows us to update state while executing the workflow. Here is basic example of a workflow to demonstrate &lt;code&gt;Seq.Fold&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let shouldContinueWorkflow (input: int) = input &amp;lt;&amp;gt; 8

let executeWorkflow =
    let inputs = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
    let shouldExecuteCurrentStep = true

    inputs
        |&amp;gt; Seq.fold (fun (shouldExecuteCurrentStep: bool) (x: int) -&amp;gt;

            match shouldExecuteCurrentStep with
            | true -&amp;gt;
                let shouldContinue = x |&amp;gt; shouldContinueWorkflow

                match shouldContinue with
                | true -&amp;gt;
                    printfn $"Continuing from input is {x}"
                | false -&amp;gt;
                    printfn $"Should not continue as input is {x}"

                shouldContinue
            | false -&amp;gt; false
        ) shouldExecuteCurrentStep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Iterate over the input of an array of integers [1...10]&lt;/li&gt;
&lt;li&gt;Check if we should continue (defaulted to true)&lt;/li&gt;
&lt;li&gt;Check if the current iteration of input is not equal to 8&lt;/li&gt;
&lt;li&gt;Set the state variable &lt;code&gt;shouldExecuteCurrentStep&lt;/code&gt; to the result of this check&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we run the above program we get the output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Continuing as input is 1
Continuing as input is 2
Continuing as input is 3
Continuing as input is 4
Continuing as input is 5
Continuing as input is 6
Continuing as input is 7
Should not continue as input is 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a basic example but next we will go into something that matches task more closely&lt;/p&gt;

&lt;h2&gt;
  
  
  Detailed workflow example
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Models
&lt;/h3&gt;

&lt;p&gt;We are going to use more complex objects for&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passing in request data to the workflow functions&lt;/li&gt;
&lt;li&gt;Returning data from the workflow validation&lt;/li&gt;
&lt;li&gt;What we pass into the workflow to iterate over&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Request
&lt;/h4&gt;

&lt;p&gt;In a real world scenario we would be dealing with real data and not an array of 1 to 10. To make this example seem closer to a real work scenario, I am going to imagine the a request will come in that contains real world data. This is the data we will use to decide if a service is called and if we should continue&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type ExternalServiceRequest = {
    EmailAddress: string
    PhoneNumber: string
    PostCode: string
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Result from the validation step
&lt;/h4&gt;

&lt;p&gt;This will hold the state data from the validation step that gets executed on each iteration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type ExternalServiceCanExecuteResult = {
    CanExecute: bool
    ShouldContinue: bool
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CanExecute&lt;/code&gt; - Can this service be called&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ShouldContinue&lt;/code&gt; - Should we continue with other workflow steps after calling the service&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  External service
&lt;/h4&gt;

&lt;p&gt;These models take the input of &lt;code&gt;ExternalServiceRequest&lt;/code&gt; and have 2 functions&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CanExecute&lt;/code&gt; - Take &lt;code&gt;ExternalServiceRequest&lt;/code&gt; and decide if we should call the service&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Execute&lt;/code&gt; - Call the service. For now we are just mocking the result of this&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since we have several services that we want to call, it would make sense that these services would inherit from some sort of base class. I used OO to achieve this and created an abstract class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type IExternalService =
    abstract member CanExecute : ExternalServiceRequest -&amp;gt; ExternalServiceCanExecuteResult
    abstract member Execute : ExternalServiceRequest -&amp;gt; unit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are a couple of implementations of this abstract class. Please see the attached git repo to view all the implementations&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let serviceA = {
    new IExternalService with                            
        member _.CanExecute request: ExternalServiceCanExecuteResult =
              match
                  request.EmailAddress.Length &amp;gt; 0,
                  request.PhoneNumber.Length &amp;gt; 0,
                  request.PostCode.Length &amp;gt; 0 with                
              | true, true, true -&amp;gt; { CanExecute = true; ShouldContinue =  false }
              | _, _, _ -&amp;gt; { CanExecute = false; ShouldContinue =  true }

        member _.Execute request = printfn "Executing service A"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each service implements the abstract class and it's functions.&lt;/p&gt;

&lt;p&gt;Taking &lt;code&gt;serviceA&lt;/code&gt; as an example as its &lt;code&gt;CanExecute&lt;/code&gt; function is more complex. This fictional service requires email, phone and postcode to be populated in order to execute. If all are populated, then we can call this service and there is no need to call any other service as we get all the required info from this service. If any of the fields are not populated then we do not call this service, but we can continue to call the next service in the workflow&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let serviceB = {
        new IExternalService with                            
            member _.CanExecute request: ExternalServiceCanExecuteResult =
                  match request.PostCode.Length &amp;gt; 0 with
                  | true -&amp;gt; { CanExecute = true; ShouldContinue =  true }
                  | false -&amp;gt; { CanExecute = false; ShouldContinue =  true }                 

            member _.Execute request = printfn "Executing service B"
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;serviceB&lt;/code&gt; is another example, but this only requires postcode to be populated. If postcode is populated then we can call this service, otherwise we continue the workflow.&lt;/p&gt;

&lt;p&gt;Here are the rest of the implementations&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let serviceC = {
    new IExternalService with                            
        member _.CanExecute request: ExternalServiceCanExecuteResult =
              match request.EmailAddress.Length &amp;gt; 0 with                
              | true -&amp;gt; { CanExecute = true; ShouldContinue =  true }
              | false -&amp;gt; { CanExecute = false; ShouldContinue =  true }

        member _.Execute request = printfn "Executing service C"
}

let serviceD = {
    new IExternalService with                            
        member _.CanExecute request: ExternalServiceCanExecuteResult =
              match request.PhoneNumber.Length &amp;gt; 0 with                
              | true -&amp;gt; { CanExecute = true; ShouldContinue =  true }
              | false -&amp;gt; { CanExecute = false; ShouldContinue =  true }

        member _.Execute request = printfn "Executing service D"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Executing the workflow
&lt;/h3&gt;

&lt;p&gt;First we have our collection of services we want to call&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let externalServices: IExternalService seq =
    [
        serviceA
        serviceB
        serviceC
        serviceD
    ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the full workflow execution using the above services collection&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let executeWorkflow
    (request: ExternalServiceRequest) =

        let shouldExecuteCurrentStep = true

        externalServices
            |&amp;gt; Seq.fold (fun (shouldExecuteCurrentStep: bool) (externalService: IExternalService) -&amp;gt;

                match shouldExecuteCurrentStep with
                | true -&amp;gt;
                    let result = request |&amp;gt; externalService.CanExecute

                    match result.CanExecute with
                    | true -&amp;gt; externalService.Execute request
                    | false -&amp;gt; printfn $"Skipping {externalService.GetType().Name}"

                    result.ShouldContinue
                | false -&amp;gt; false
            ) shouldExecuteCurrentStep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving to a pattern like this made the code more concise and readable. One of the big things we are striving for is developer experience. A big factor in this is how we write and structure our code. If a developer looks at the codebase we want them to feel comfortable and confident that they can contribute.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Add deployment annotations to grafana dashboards</title>
      <dc:creator>Wesley Skeen</dc:creator>
      <pubDate>Fri, 19 May 2023 14:57:59 +0000</pubDate>
      <link>https://dev.to/wesley_skeen/add-deployment-annotations-to-grafana-dashboards-hag</link>
      <guid>https://dev.to/wesley_skeen/add-deployment-annotations-to-grafana-dashboards-hag</guid>
      <description>&lt;p&gt;When noticing spikes in exceptions on our dashboards, one of the first things we do is check if there has been a recent deployment. Checking to see if a deployment caused the spikes.&lt;/p&gt;

&lt;p&gt;This can be straightforward, but if you have many teams deploying many apps all throughout the day, it may be a little trickier tracking down a related deployment. &lt;/p&gt;

&lt;p&gt;I am going to talk you through how to add a grafana annotation to visualize when a deployment has taken place. This will be a simple annotation with some tags. &lt;/p&gt;

&lt;p&gt;Here is what we are gonna create&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucnjhqm1665g0bql2zl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucnjhqm1665g0bql2zl8.png" alt="grafana annotation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First of all, I am going to be running this using my grafana playground which you can find here&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/wesley_skeen" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F976458%2F23a37298-e7b5-4545-8b63-dd1aa26f00bb.jpeg" alt="wesley_skeen"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/wesley_skeen/setup-grafana-locally-with-some-data-sources-4mig" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Setup Grafana, Jaeger &amp;amp; Zipkin locally&lt;/h2&gt;
      &lt;h3&gt;Wesley Skeen ・ Nov 21 '22&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#grafana&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#dotnet&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Once you have that set up, there are a few steps we need to do to enable annotations to display on a dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a service account
&lt;/h3&gt;

&lt;p&gt;Browse &lt;a href="http://localhost:3000/org/serviceaccounts" rel="noopener noreferrer"&gt;here&lt;/a&gt; and click &lt;strong&gt;Add service account&lt;/strong&gt;. If you have the grafana playground running, you should be able to access the link.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvj8qu7yogk8eu5g7tcvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvj8qu7yogk8eu5g7tcvj.png" alt="add service account"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give it a name and the &lt;strong&gt;Editor&lt;/strong&gt; role&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvv8mgt721kktlri3i5w6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvv8mgt721kktlri3i5w6.png" alt="add name and editor role"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a service account token
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;Add service account token&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjtio368690i9d6eob1x2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjtio368690i9d6eob1x2.png" alt="Add service account token button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click &lt;strong&gt;Generate token&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmr8kndkbuxecrly9si8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmr8kndkbuxecrly9si8a.png" alt="generate token"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have this token, copy it and keep it save.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a dashboard
&lt;/h3&gt;

&lt;p&gt;For this example, I am just gonna create a really simple dashboard here with a random metric. Just to have something to show.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwnp6f7hhwpvu5aohie4k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwnp6f7hhwpvu5aohie4k.png" alt="dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create annotations
&lt;/h3&gt;

&lt;p&gt;Next we will create some annotations to display. We are going to send them as a http post request. The URL we create annotations on is &lt;code&gt;http://localhost:3000/api/annotations&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We next have to add the &lt;code&gt;Authorization&lt;/code&gt; header. The value for this will be &lt;code&gt;Bearer {your service account token we created}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The last thing to do is send a request. The payload will look like&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "created": EPOCH with milliseconds, // eg 1684502867000
  "updated": EPOCH with milliseconds,
  "time": EPOCH with milliseconds,
  "timeEnd": EPOCH with milliseconds,
  "text": "Deployment", // text to display on the annotation pop up
  "tags": [ // tags to display on the annotation pop up
    "kind:deployment",
    "project:your-project-or-app-name",
    "env:prod",
    "region:eu-west-1"
  ]
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The response should be &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
    "id": {annotation_id},
    "message": "Annotation added"
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now that we have created an annotation, it is time to set up our dashboard to view them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up dashboard annotations
&lt;/h3&gt;

&lt;p&gt;Go to the dashboard you created previously and go to it's settings.&lt;/p&gt;

&lt;p&gt;Next click on &lt;strong&gt;Annotations&lt;/strong&gt; and then &lt;strong&gt;Add annotation query&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhi5pv58mg6hd2611ebag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhi5pv58mg6hd2611ebag.png" alt="add annotation query"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chose &lt;strong&gt;Grafana&lt;/strong&gt; as the data source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gh24ktq8sgzdgk0gipx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gh24ktq8sgzdgk0gipx.png" alt="Grafana datasource"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next under &lt;strong&gt;Filter by&lt;/strong&gt;, choose &lt;strong&gt;Tags&lt;/strong&gt; and then turn on &lt;strong&gt;Match any&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmve439o4n10yszo8gq4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmve439o4n10yszo8gq4t.png" alt="tags filter"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then filter by tags you sent when sending the http request. I am just gonna filter on one tag &lt;code&gt;kind:deployment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gpulpttfemaxw0a6bhy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gpulpttfemaxw0a6bhy.png" alt="tags"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Apply&lt;/strong&gt; and &lt;strong&gt;Save dashboard&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Browse back to your dashboard and you now should see an annotation being displayed&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucnjhqm1665g0bql2zl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucnjhqm1665g0bql2zl8.png" alt="grafana annotation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don't see any annotations check the following things&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are using a recent timestamp for your EPOCH time when creating your annotations&lt;/li&gt;
&lt;li&gt;You are filtering by the correct tags&lt;/li&gt;
&lt;li&gt;Your dashboard has annotations turned on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabm4337tm059wkguaazf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabm4337tm059wkguaazf.png" alt="annotations switch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding to Azure DevOps pipeline
&lt;/h3&gt;

&lt;p&gt;I have created a &lt;a href="https://gist.github.com/WesleySkeen/c197ead0e0cc3887d1f56945e83abaea" rel="noopener noreferrer"&gt;gist&lt;/a&gt; that can be added to your Azure DevOps pipeline.&lt;/p&gt;

</description>
      <category>monitoring</category>
    </item>
    <item>
      <title>F# Orleans reminder grain</title>
      <dc:creator>Wesley Skeen</dc:creator>
      <pubDate>Fri, 21 Apr 2023 09:04:51 +0000</pubDate>
      <link>https://dev.to/wesley_skeen/f-orleans-reminder-grain-4k7k</link>
      <guid>https://dev.to/wesley_skeen/f-orleans-reminder-grain-4k7k</guid>
      <description>&lt;p&gt;This is a simple example of how we set up reminder grains in Orleans and f#.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/PiotrJustyna/road-to-orleans/tree/main/1b" rel="noopener noreferrer"&gt;Here's&lt;/a&gt; the code so you can follow along&lt;/p&gt;

&lt;h2&gt;
  
  
  Grain interface
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type IYourReminderGrain =
    inherit IGrainWithStringKey
    inherit IRemindable

    abstract member WakeUp : unit -&amp;gt; Task
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We have 2 important parts here that will be implemented in the grain.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;IRemindable&lt;/code&gt;: This forces us to implement the &lt;code&gt;ReceiveReminder&lt;/code&gt; function. This is where the logic we want to execute each time the reminder gets triggered.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WakeUp&lt;/code&gt; : This activates the grain so we can set up the reminder&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Grain implementation
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type YourReminderGrain() =
    inherit Grain()
    interface IYourReminderGrain with
        member this.WakeUp() = Task.CompletedTask
        member this.ReceiveReminder(reminderName, status) =
            Console.WriteLine(reminderName, status)
            Task.CompletedTask

    override _.OnActivateAsync(cancellationToken:CancellationToken) = 
        let _periodInSeconds = TimeSpan.FromSeconds 60
        let _dueInSeconds = TimeSpan.FromSeconds 60
        let _reminder = base.RegisterOrUpdateReminder(base.GetPrimaryKeyString(), _dueInSeconds, _periodInSeconds)
        base.OnActivateAsync(cancellationToken)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There are 2 important parts here&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement the &lt;code&gt;IYourReminderGrain&lt;/code&gt; : The &lt;code&gt;WakeUp&lt;/code&gt; is simple as we do not want to do anything here. &lt;code&gt;ReceiveReminder&lt;/code&gt; contains our logic. What we want to execute on every trigger of the reminder. Here we are simply writing the reminder name to the console.&lt;/li&gt;
&lt;li&gt;Overriding &lt;code&gt;OnActivateAsync&lt;/code&gt; : This is where we register the reminder and specify at what interval it should trigger at. &lt;code&gt;_periodInSeconds&lt;/code&gt; specifies the time between triggers and &lt;code&gt;_dueInSeconds&lt;/code&gt; specifies how long to wait until the first trigger&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Kicking off the reminder grain
&lt;/h2&gt;

&lt;p&gt;In this example I do this by adding a hosted service &lt;code&gt;GrainActivatorHostedService&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is responsible for retrieving the reminder grain from the &lt;code&gt;IGrainFctory&lt;/code&gt; and calling the &lt;code&gt;WakeUp&lt;/code&gt; grain function&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let! _ =
    _client
        .GetGrain&amp;lt;IYourReminderGrain&amp;gt;(Guid.NewGuid().ToString())
                    .WakeUp() 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it. To run the app just run&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; sh run-silo-local.sh
or
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; sh run-silo-docker.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can also run it in your IDE or VS Code devcontainers&lt;/p&gt;

&lt;p&gt;To verify everything is working correctly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dashboard: &lt;a href="http://localhost:8081" rel="noopener noreferrer"&gt;http://localhost:8081&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dashboard if running with Docker &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is part of the road-to-orleans project. Check it out&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/PiotrJustyna" rel="noopener noreferrer"&gt;
        PiotrJustyna
      &lt;/a&gt; / &lt;a href="https://github.com/PiotrJustyna/road-to-orleans" rel="noopener noreferrer"&gt;
        road-to-orleans
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This repository illustrates the road to orleans with practical, real-life examples. From most basic, to more advanced techniques.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/PiotrJustyna/road-to-orleans#road-to-orleans" rel="noopener noreferrer"&gt;road to orleans&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/PiotrJustyna/road-to-orleans#build--run" rel="noopener noreferrer"&gt;build &amp;amp; run&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/PiotrJustyna/road-to-orleans#monitoring" rel="noopener noreferrer"&gt;monitoring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/PiotrJustyna/road-to-orleans#code" rel="noopener noreferrer"&gt;code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/PiotrJustyna/road-to-orleans#further-reading" rel="noopener noreferrer"&gt;further reading&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;road to orleans&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://gitter.im/road-to-orleans/community?utm_source=badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=pr-badge" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/aa928819c2f909e09e26fbf1946037e9ee7a12b3976ee08c96fb540b37864138/68747470733a2f2f6261646765732e6769747465722e696d2f726f61642d746f2d6f726c65616e732f636f6d6d756e6974792e737667" alt="Gitter"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This repository illustrates the road to orleans with practical, real-life examples such as .NET solutions. From most basic to more advanced techniques. The code is written using .NET 6 and was tested on macOS (Catalina 10.15.7) and, wherever docker is supported, Linux (Alpine 3.12).&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;build &amp;amp; run&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;IDE: build + run (first the cluster, then the client)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run-[client/silo]-[docker/local].sh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;monitoring&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Silo dashboards are available by default on &lt;code&gt;localhost:8080&lt;/code&gt; unless configured otherwise in the code/&lt;code&gt;dockerfile&lt;/code&gt;/&lt;code&gt;run-[client/silo]-[docker/local].sh&lt;/code&gt;. Additionally, Datadog APM support is illustrated in solution 3b.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;code&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
  &lt;div class="js-render-enrichment-target"&gt;
&lt;br&gt;
    &lt;div class="render-plaintext-hidden"&gt;
&lt;br&gt;
      &lt;pre&gt;flowchart TD&lt;br&gt;
Solution1(Solution1: One basic silo, no grains.)&lt;br&gt;
Solution2(Solution2: One basic silo, one grain, one console client.)&lt;br&gt;
Solution3(Solution3: One basic silo, one grain, one console client, &amp;lt;br/&amp;gt;everything containerized.)&lt;br&gt;
Solution3a(Solution3a: Solution3 + grain persistence.)&lt;br&gt;
Solution3b(Solution3b: Solution3 + datadog APM.)&lt;br&gt;
Solution4(Solution4: First in-memory clustering example. &amp;lt;br/&amp;gt;Many silos, many clients.)&lt;br&gt;
Solution5(Solution5: Solution4 where&lt;/pre&gt;…&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/PiotrJustyna/road-to-orleans" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>dotnet</category>
      <category>orleans</category>
      <category>fsharp</category>
    </item>
    <item>
      <title>Detecting PII leakage in logs</title>
      <dc:creator>Wesley Skeen</dc:creator>
      <pubDate>Mon, 13 Mar 2023 09:00:29 +0000</pubDate>
      <link>https://dev.to/wesley_skeen/detecting-pii-leakage-in-logs-g6e</link>
      <guid>https://dev.to/wesley_skeen/detecting-pii-leakage-in-logs-g6e</guid>
      <description>&lt;p&gt;First I wanted to mention I collaborated on this project and article with &lt;a class="mentioned-user" href="https://dev.to/mereta"&gt;@mereta&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Before we begin, I want to direct you to the post I published to set up grafana locally using docker. Here you will find simple steps to get your environment set up to experiment. &lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/wesley_skeen" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F976458%2F23a37298-e7b5-4545-8b63-dd1aa26f00bb.jpeg" alt="wesley_skeen"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/wesley_skeen/setup-grafana-locally-with-some-data-sources-4mig" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Setup Grafana, Jaeger &amp;amp; Zipkin locally&lt;/h2&gt;
      &lt;h3&gt;Wesley Skeen ・ Nov 21 '22&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#grafana&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#dotnet&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Once you have this running, I want to direct you towards the &lt;code&gt;promtail.yml&lt;/code&gt; file. This is what we are going to change to let promtail apply our PII detection logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipeline Stages
&lt;/h2&gt;

&lt;p&gt;We are going to add &lt;code&gt;pipeline_stages&lt;/code&gt; to this file.&lt;/p&gt;

&lt;p&gt;Simply put, each log that gets passed through promtail will go through these stages. We can perform a number of actions that you can read in detail about &lt;a href="https://grafana.com/docs/loki/latest/clients/promtail/pipelines/" rel="noopener noreferrer"&gt;here&lt;/a&gt; in the grafana docs, but I will go through stages to&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect PII&lt;/li&gt;
&lt;li&gt;Validate the result of the detection&lt;/li&gt;
&lt;li&gt;Create a label to hold the result&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Detect PII
&lt;/h3&gt;

&lt;p&gt;As part of the &lt;code&gt;stages&lt;/code&gt; section, we added the regex stage&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- regex:
    expression: '(?P&amp;lt;sensitive_email&amp;gt;([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+))'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we add an &lt;code&gt;expression&lt;/code&gt;. This is built up of 2 parts&lt;br&gt;
&lt;code&gt;(?P&amp;lt;{0}&amp;gt;({1}))&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0 - This is the variable that holds the result of the regex match&lt;/li&gt;
&lt;li&gt;1 - This is the actual regex used on the log content&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Validate the result of the detection
&lt;/h3&gt;

&lt;p&gt;Next we have the template stage&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- template:
    source: sensitive_email
    template: '{{ not (empty .Value) }}' 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stage takes the result held in the variable that was set in the regex stage and applies some logic to it. This logic also updates the value of the variable.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Log&lt;/th&gt;
&lt;th&gt;value in &lt;code&gt;sensitive_email&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;&lt;code&gt;{{ not (empty .Value) }}&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;sensitive_email&lt;/code&gt; new value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;My email is &lt;a href="mailto:JP@mail.com"&gt;JP@mail.com&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:JP@mail.com"&gt;JP@mail.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;My email is ***&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Create a label to hold the result
&lt;/h3&gt;

&lt;p&gt;For this all we have to do is add the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- labels:
    sensitive_email:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds a label to the log and sets its value to what is held in &lt;code&gt;sensitive_email&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example of it working
&lt;/h3&gt;

&lt;p&gt;I added a log in my API&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_logger.LogInformation($"my data is JP@mail.com");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the result in Loki&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tpylxvqwumg1ayh3jd2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tpylxvqwumg1ayh3jd2.png" alt="Image of Loki with example of PII flagging"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the log line is&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F189nu8ihx353z5z1258j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F189nu8ihx353z5z1258j.png" alt="Close up image of the log content"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and the value of &lt;code&gt;sensitive_email&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzkpcbgwi347ofr93ed6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzkpcbgwi347ofr93ed6.png" alt="Close up image of label value"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  New content of &lt;code&gt;promtail.yml&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;With the above addition of &lt;code&gt;pipeline_stages&lt;/code&gt; this file should look like. I have added another example of detecting credit card PII.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: system
    pipeline_stages:      
      - match:
          pipeline_name: "security"
          selector: '{app="api"}'
          stages:

            - regex:
                expression: '(?P&amp;lt;sensitive_creditcard&amp;gt;(?:\d[ -]*?){13,16})'
            - regex:
                expression: '(?P&amp;lt;sensitive_email&amp;gt;([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+))'

            - template:
                source: sensitive_creditcard
                template: '{{ not (empty .Value) }}'
            - template:
                source: sensitive_email
                template: '{{ not (empty .Value) }}'            

            - labels:
                sensitive_creditcard:
                sensitive_email:

    static_configs:
      - targets:
          - localhost
        labels:
          job: varlogs
          __path__: /var/log/*local.log
          app: 'api'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the results of these stages
&lt;/h2&gt;

&lt;p&gt;There are several things you can do with these new log labels. Among others, you could&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an alert to detect if PII has leaked into your logs.&lt;/li&gt;
&lt;li&gt;Create dashboards to monitor base on the new labels&lt;/li&gt;
&lt;li&gt;You can do some interesting things in grafana such as route these logs to a different tenant. This tenant would have special privileges to view logs with PII contained.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Improvements
&lt;/h2&gt;

&lt;p&gt;Merge the results of the regex matches into a single label. &lt;/p&gt;

&lt;p&gt;First we need to update the source template to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- template:
    source: sensitive_email
    template: '{{ if not (empty .Value) }} true {{ end }}' 

- template:
     source: sensitive_creditcard
     template: '{{ if not (empty .Value) }} true {{ end }}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then we add a new source template to merge the results&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- template:
     source: sensitive
     template: '{{ or .sensitive_email .sensitive_creditcard false }}'

- labels:
     sensitive:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>grafana</category>
    </item>
    <item>
      <title>Setup Grafana, Jaeger &amp; Zipkin locally</title>
      <dc:creator>Wesley Skeen</dc:creator>
      <pubDate>Mon, 21 Nov 2022 16:17:59 +0000</pubDate>
      <link>https://dev.to/wesley_skeen/setup-grafana-locally-with-some-data-sources-4mig</link>
      <guid>https://dev.to/wesley_skeen/setup-grafana-locally-with-some-data-sources-4mig</guid>
      <description>&lt;p&gt;Here is an easy way to setup Grafana locally using &lt;code&gt;docker&lt;/code&gt; and &lt;code&gt;dotnet&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/WesleySkeen"&gt;
        WesleySkeen
      &lt;/a&gt; / &lt;a href="https://github.com/WesleySkeen/Grafana-Data-Sources"&gt;
        Grafana-Data-Sources
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Grafana-Data-Sources&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;The current set up in this repo is very minimal. I am just touching the surface of what is possible. I wanted to demonstrate how to run the services locally&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
Build and run the api with docker on local port 4000&lt;/h3&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; docker build --pull --no-cache -t weather_forecast_api -f ops/docker/Dockerfile &lt;span class="pl-c1"&gt;.&lt;/span&gt;
&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; docker run -p 4000:5000 --rm weather_forecast_api&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;You should now be able to view OTEL metrics from the API&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; curl http://localhost:4000/metrics

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; TYPE process_runtime_dotnet_gc_collections_count counter&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; HELP process_runtime_dotnet_gc_collections_count Number of garbage collections that have occurred since process start.&lt;/span&gt;
process_runtime_dotnet_gc_collections_count{generation=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;gen2&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;} 0 1665568680770
process_runtime_dotnet_gc_collections_count{generation=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;gen1&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;} 0 1665568680770
process_runtime_dotnet_gc_collections_count{generation=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;gen0&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;} 0 1665568680770

&lt;span class="pl-c1"&gt;.&lt;/span&gt;
&lt;span class="pl-c1"&gt;.&lt;/span&gt;
&lt;span class="pl-c1"&gt;.&lt;/span&gt;

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; EOF&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
Build and run the api with docker compose&lt;/h3&gt;
&lt;p&gt;Using docker compose, we can combine multiple docker containers into a single deployment
These containers can communicate with each other since we can specify what…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/WesleySkeen/Grafana-Data-Sources"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Please refer to the above repo for the source code to run the below commands and follow along. &lt;/p&gt;

&lt;h3&gt;
  
  
  Build and run the api with docker on local port 4000
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; docker build &lt;span class="nt"&gt;--pull&lt;/span&gt; &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; weather_forecast_api &lt;span class="nt"&gt;-f&lt;/span&gt; ops/docker/Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 4000:5000 &lt;span class="nt"&gt;--rm&lt;/span&gt; weather_forecast_api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now be able to view OTEL metrics from the API&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; curl http://localhost:4000/metrics

&lt;span class="c"&gt;# TYPE process_runtime_dotnet_gc_collections_count counter&lt;/span&gt;
&lt;span class="c"&gt;# HELP process_runtime_dotnet_gc_collections_count Number of garbage collections that have occurred since process start.&lt;/span&gt;
process_runtime_dotnet_gc_collections_count&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;generation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gen2"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 0 1665568680770
process_runtime_dotnet_gc_collections_count&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;generation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gen1"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 0 1665568680770
process_runtime_dotnet_gc_collections_count&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;generation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gen0"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 0 1665568680770

&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Build and run the api with docker compose
&lt;/h3&gt;

&lt;p&gt;Using docker compose, we can combine multiple docker containers into a single deployment.&lt;br&gt;
These containers can communicate with each other since we can specify what network they can be added to.&lt;/p&gt;

&lt;p&gt;First build the image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; docker build &lt;span class="nt"&gt;--pull&lt;/span&gt; &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; weather_forecast_api &lt;span class="nt"&gt;-f&lt;/span&gt; ops/docker/Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in the docker folder (&lt;code&gt;ops/docker&lt;/code&gt;), run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Among other things, this will bring up Promethius. We have set Promethius up to scrape metrics from the &lt;code&gt;/metrics&lt;/code&gt; endpoint of the weather forecaset API.&lt;br&gt;
Notice in the &lt;code&gt;promethius.yml&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      
      &lt;span class="c1"&gt;# replace the IP with your local IP for development&lt;/span&gt;
      &lt;span class="c1"&gt;# localhost is not it, as that is w/in the container :)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;weather_forecast_api:5000'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To confirm promethius can access the API metric scraping endpoint, browse to Browse to the &lt;a href="http://localhost:9090/targets"&gt;targets page&lt;/a&gt;. You should see &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T-sRDMk7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqxok5u3dug4x4k2h42x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T-sRDMk7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqxok5u3dug4x4k2h42x.png" alt="Promethius targets page" width="800" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that the &lt;code&gt;Status&lt;/code&gt; is &lt;code&gt;UP&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Grafana set up
&lt;/h2&gt;

&lt;p&gt;Browse to the &lt;a href="http://localhost:3000/datasources"&gt;datasources page&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;username : admin &lt;/p&gt;

&lt;p&gt;password : P@ssw0rd&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Promethius
&lt;/h3&gt;

&lt;p&gt;Set the URL to &lt;a href="http://prometheus:9090"&gt;http://prometheus:9090&lt;/a&gt; and then click &lt;code&gt;Save &amp;amp; Test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then go to the &lt;a href="http://localhost:3000/explore"&gt;explore page&lt;/a&gt; and select promethius as the data source. You should be able to access the metrics &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JoOUr0Ib--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/871a104tiac6zxskpr0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JoOUr0Ib--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/871a104tiac6zxskpr0f.png" alt="Promethius metrics" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Custom metrics
&lt;/h4&gt;

&lt;p&gt;As an example, I have added a custom meter counter that we can use to track the number of requests made to &lt;code&gt;/WeatherForecast&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;WeatherForecastController.cs&lt;/code&gt; We have&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;SharedTelemetryUtilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will add a new metric with a name we have specified &lt;code&gt;data.request_counter&lt;/code&gt;&lt;br&gt;
We can now view this custom metric in Grafana&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cTyoqfOR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l92o3d7pmeq3kn8yuxvc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cTyoqfOR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l92o3d7pmeq3kn8yuxvc.png" alt="Promethius custom metric" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Loki
&lt;/h3&gt;

&lt;p&gt;Set the URL to &lt;a href="http://loki:3100"&gt;http://loki:3100&lt;/a&gt; and then click &lt;code&gt;Save &amp;amp; Test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then go to the &lt;a href="http://localhost:3000/explore"&gt;explore page&lt;/a&gt; and select loki as the data source. Under labels, select filename. You should have have a log file accessable.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F4GAKMbO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oagovpfer8bd9e2uewsg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F4GAKMbO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oagovpfer8bd9e2uewsg.png" alt="Loki dashboard" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Traces
&lt;/h2&gt;

&lt;p&gt;In the endpoint &lt;code&gt;/WeatherForecast&lt;/code&gt; I added some external calls to show traces in action. Once you make some calls to this endpoint, you will start to generate traces&lt;/p&gt;

&lt;h3&gt;
  
  
  Zipkin
&lt;/h3&gt;

&lt;p&gt;Browse to &lt;a href="http://localhost:9411"&gt;zipkin&lt;/a&gt; and click &lt;code&gt;RUN QUERY&lt;/code&gt;. This should display some traces&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lPKGJjI1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yvnrh9illdb17pbh6hxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lPKGJjI1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yvnrh9illdb17pbh6hxw.png" alt="Zipkin dashboard" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking one of these traces will give you more detail. As you can see, there is an over all request and the 2 external calls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IFOBR02R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/et70m498u4h2tln6lumr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IFOBR02R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/et70m498u4h2tln6lumr.png" alt="Zipkin trace" width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Jaeger
&lt;/h3&gt;

&lt;p&gt;Browse to &lt;a href="http://localhost:16686"&gt;Jaeger&lt;/a&gt;, select a service and click &lt;code&gt;Find Traces&lt;/code&gt;. This should display some traces&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hAzxdOuC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ehrt4g3ew0a28fna2dq3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hAzxdOuC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ehrt4g3ew0a28fna2dq3.png" alt="Jaeger dashboard" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similar to Zipkin, clicking on one of the traces will give more detail&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vswn56W2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztgp1jnqdjxwyewjz79x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vswn56W2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztgp1jnqdjxwyewjz79x.png" alt="Jaeger trace" width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>grafana</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
