Skip to content

Commit

Permalink
Exporter configuration (#2)
Browse files Browse the repository at this point in the history
* First draft of configuration design

* Add support for Grafana-specific environment variables

* Add modular extension method for initializing the exporter

* Wire up and add API documentation

* Add unit tests

* Make sure to append signal specific paths

* Add a README

* CI fixes

* Add tests

* .NET format fixes

* Multi-targeting

* Add net7.0 as target framework for tests

* Apply .NET format fixes

* .NET format fixes

* .NET format fixes
  • Loading branch information
pyohannes authored Sep 1, 2023
1 parent 27b4516 commit 747feae
Show file tree
Hide file tree
Showing 13 changed files with 583 additions and 0 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Build

on:
push:
branches: [ 'main*' ]
paths-ignore:
- '**.md'
pull_request:
branches: [ 'main*' ]
paths-ignore:
- '**.md'

jobs:
build-test:
strategy:
fail-fast: false # ensures the entire test matrix is run, even if one permutation fails
matrix:
os: [ windows-latest, ubuntu-latest ]
version: [ net462, net6.0, net7.0 ]
exclude:
- os: ubuntu-latest
version: net462

runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # fetching all

- name: Install dependencies
run: dotnet restore

- name: Build
run: dotnet build --configuration Release --no-restore

- name: Test ${{ matrix.version }}
run: dotnet test **/bin/**/${{ matrix.version }}/*.Tests.dll --logger:"console;verbosity=detailed"
36 changes: 36 additions & 0 deletions GrafanaOpenTelemetry.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F0687CB8-95E1-4372-9444-70676DE3A34A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grafana.OpenTelemetry", "src\Grafana.OpenTelemetry\Grafana.OpenTelemetry.csproj", "{B4761520-2B6F-4605-BC3B-66710F7439EA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FB0399BE-6925-42B7-8431-C5A6E21DC8EC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grafana.OpenTelemetry.Tests", "tests\Grafana.OpenTelemetry.Tests\Grafana.OpenTelemetry.Tests.csproj", "{30810D69-3237-4260-93C2-DC601C5AC80F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B4761520-2B6F-4605-BC3B-66710F7439EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4761520-2B6F-4605-BC3B-66710F7439EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4761520-2B6F-4605-BC3B-66710F7439EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4761520-2B6F-4605-BC3B-66710F7439EA}.Release|Any CPU.Build.0 = Release|Any CPU
{30810D69-3237-4260-93C2-DC601C5AC80F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30810D69-3237-4260-93C2-DC601C5AC80F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30810D69-3237-4260-93C2-DC601C5AC80F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30810D69-3237-4260-93C2-DC601C5AC80F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B4761520-2B6F-4605-BC3B-66710F7439EA} = {F0687CB8-95E1-4372-9444-70676DE3A34A}
{30810D69-3237-4260-93C2-DC601C5AC80F} = {FB0399BE-6925-42B7-8431-C5A6E21DC8EC}
EndGlobalSection
EndGlobal
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Grafana distribution of OpenTelemetry .NET instrumentation

This is a pre-configured and pre-packaged bundle of OpenTelemetry .NET
components, optimized for the Grafana Stack.

## Getting Started

### Step 1: Install package

### Step 2: Enable the Grafana distribution at application startup

The `UseGrafana` extension method on the `TracerProviderBuilder` or the
`MetricProviderBuilder` can be used to set up the Grafana distribution. By
default, telemetry data will be sent to a Grafana agent or an OTel collector
that runs locally and listens to default OTLP ports:

```csharp
using OpenTelemetry;
using OpenTelemetry.Trace;
using Grafana.OpenTelemetry;

public class Program
{
public static void Main(string[] args)
{
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.UseGrafana()
.Build();
}
}
```

Given the zone, instance id, and API token, telemetry data can be sent directly
to the Grafana Cloud without involving an agent or collector:

```csharp
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.UseGrafana(config =>
{
config.ExporterSettings = new CloudOtlpExporter
{
Zone = "prod-us-east-0",
InstanceId = "123456",
ApiKey = "a-secret-token"
};
})
.Build();
```
76 changes: 76 additions & 0 deletions src/Grafana.OpenTelemetry/ExporterSettings/AgentOtlpExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

namespace Grafana.OpenTelemetry
{
/// <summary>
/// Settings for exporting telemetry to a Grafana Agent or collector.
/// </summary>
public class AgentOtlpExporter : ExporterSettings
{
/// <summary>
/// Gets or sets the address of the Grafana Agent or collector. If not set, the OpenTelemetry
/// default is used (`http://localhost:4817` for http/protobuf, and `http://localhost:4818`
/// for grpc).
/// </summary>
public Uri Endpoint { get; set; }

/// <summary>
/// The OTLP protocol to be used for exporting telemetry data.
/// </summary>
public OtlpExportProtocol Protocol { get; set; }

/// <inheritdoc/>
override internal void Apply(TracerProviderBuilder builder)
{
if (EnableTraces == false)
{
return;
}

builder.AddOtlpExporter(config => ApplyToConfig(config));
}

/// <inheritdoc/>
override internal void Apply(MeterProviderBuilder builder)
{
if (EnableMetrics == false)
{
return;
}

builder.AddOtlpExporter(config => ApplyToConfig(config));
}

/// <inheritdoc/>
override internal void Apply(ILoggingBuilder builder)
{
if (EnableLogs == false)
{
return;
}

builder.AddOpenTelemetry(options =>
{
options.AddOtlpExporter(config => ApplyToConfig(config));
});
}

private void ApplyToConfig(OtlpExporterOptions options)
{
if (Endpoint != null)
{
options.Endpoint = Endpoint;
}

if (Protocol != null)
{
options.Protocol = (OtlpExportProtocol)Protocol;
}
}
}
}
105 changes: 105 additions & 0 deletions src/Grafana.OpenTelemetry/ExporterSettings/CloudOtlpExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

namespace Grafana.OpenTelemetry
{
/// <summary>
/// Settings for exporting telemetry directly to Grafana Agent via OTLP.
/// </summary>
public class CloudOtlpExporter : ExporterSettings
{
internal const string ZoneEnvVarName = "GRAFANA_OTLP_CLOUD_ZONE";
internal const string InstanceIdEnvVarName = "GRAFANA_OTLP_CLOUD_INSTANCE_ID";
internal const string ApiKeyEnvVarName = "GRAFANA_OTLP_CLOUD_API_KEY";

/// <summary>
/// Gets or sets the zone of the Grafana Cloud stack.
/// </summary>
public string Zone { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the instance id of the Grafana Cloud stack.
/// </summary>
public string InstanceId { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the API key for sending data to the Grafana Cloud stack.
/// </summary>
public string ApiKey { get; set; } = string.Empty;

/// <summary>
/// Initializes a new instance of <see cref="CloudOtlpExporter"/>.
/// </summary>
public CloudOtlpExporter()
: this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
{ }

internal CloudOtlpExporter(IConfiguration configuration)
{
this.Zone = configuration[ZoneEnvVarName] ?? string.Empty;
this.InstanceId = configuration[InstanceIdEnvVarName] ?? string.Empty;
this.ApiKey = configuration[ApiKeyEnvVarName] ?? string.Empty;
}

/// <inheritdoc/>
override internal void Apply(TracerProviderBuilder builder)
{
if (EnableTraces == false)
{
return;
}

builder.AddOtlpExporter(config =>
{
var configurationHelper = new GrafanaCloudConfigurationHelper(Zone, InstanceId, ApiKey);
config.Endpoint = configurationHelper.OtlpEndpointTraces;
config.Headers = configurationHelper.OtlpAuthorizationHeader;
config.Protocol = OtlpExportProtocol.HttpProtobuf;
});
}

/// <inheritdoc/>
override internal void Apply(MeterProviderBuilder builder)
{
if (EnableMetrics == false)
{
return;
}

builder.AddOtlpExporter(config =>
{
var configurationHelper = new GrafanaCloudConfigurationHelper(Zone, InstanceId, ApiKey);
config.Endpoint = configurationHelper.OtlpEndpointMetrics;
config.Headers = configurationHelper.OtlpAuthorizationHeader;
config.Protocol = OtlpExportProtocol.HttpProtobuf;
});
}

/// <inheritdoc/>
override internal void Apply(ILoggingBuilder builder)
{
if (EnableLogs == false)
{
return;
}

builder.AddOpenTelemetry(options =>
{
options.AddOtlpExporter(config =>
{
var configurationHelper = new GrafanaCloudConfigurationHelper(Zone, InstanceId, ApiKey);
config.Endpoint = configurationHelper.OtlpEndpointLogs;
config.Headers = configurationHelper.OtlpAuthorizationHeader;
config.Protocol = OtlpExportProtocol.HttpProtobuf;
});
});
}
}
}
48 changes: 48 additions & 0 deletions src/Grafana.OpenTelemetry/ExporterSettings/ExporterSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Microsoft.Extensions.Logging;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

namespace Grafana.OpenTelemetry
{
/// <summary>
/// All OpenTelemetry exporter settings supported by Grafana need to derive from this class.
/// </summary>
public abstract class ExporterSettings
{
/// <summary>
/// Gets or sets whether traces should be sent to Grafana.
/// </summary>
public bool EnableTraces { get; set; } = true;

/// <summary>
/// Gets or sets whether metrics should be sent to Grafana.
/// </summary>
public bool EnableMetrics { get; set; } = true;

/// <summary>
/// Gets or sets whether logs should be sent to Grafana.
/// </summary>
public bool EnableLogs { get; set; } = true;

/// <summary>
/// Applies the exporter settings by initializing an exporter on the
/// given <see cref="TracerProviderBuilder"/>.
/// </summary>
/// <param name="builder">A <see cref="TracerProviderBuilder"/></param>
internal abstract void Apply(TracerProviderBuilder builder);

/// <summary>
/// Applies the exporter settings by initializing an exporter on the
/// given <see cref="MeterProviderBuilder"/>.
/// </summary>
/// <param name="builder">A <see cref="MeterProviderBuilder"/></param>
internal abstract void Apply(MeterProviderBuilder builder);

/// <summary>
/// Applies the exporter settings by initializing an exporter on the
/// given <see cref="ILoggingBuilder"/>.
/// </summary>
/// <param name="builder">A <see cref="ILoggingBuilder"/></param>
internal abstract void Apply(ILoggingBuilder builder);
}
}
Loading

0 comments on commit 747feae

Please sign in to comment.