Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Console Trace link writer #222

Merged
merged 15 commits into from
Aug 1, 2022

Conversation

martinjt
Copy link
Member

Adds a link exporter that writes all root spans to the console with a link to honeycomb for viewing it.

Copy link
Contributor

@MikeGoldsmith MikeGoldsmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution 👍🏻

I like what you've done and I think it's heading in the right direction. I've left some comments to make use of existing Options values and suggested some other things.

I've focused on external config & behaviour as internals can be updated later and this is likely a debug tool rather than being activated in production systems.

namespace Honeycomb.OpenTelemetry
{
/// <summary>
/// Writes links to the Honeycomb for all root spans
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Writes links to the Honeycomb for all root spans
/// Writes links to the Console for all root spans

/// </summary>
public class ConsoleLinkExporter : BaseExporter<Activity>
{
private readonly HoneycombOptions _options;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only need the service name outside of init, let's capture that instead directly instead of keeping a reference to the whole options object.

public class ConsoleLinkExporter : BaseExporter<Activity>
{
private readonly HoneycombOptions _options;
private string _teamSlug;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think getting the team and environment slugs could be reusable so may end up being passed in instead of being resolved inside this span processor.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we should be calling out via http as part of startup in any other environment than production. It's an async call in a sync method, and I don't like it all. As it stands, because it's in the ConsoleLinkExporter, it won't be triggered unless you ask for tracelinks.

I think that's probably an internal decision too, so we can refactor that as and when we need it.

Comment on lines 36 to 44
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://api.honeycomb.io/1/auth");
httpClient.DefaultRequestHeaders.Add("X-Honeycomb-Team", _options.ApiKey);

var response = httpClient.GetAsync("").GetAwaiter().GetResult();
if (!response.IsSuccessStatusCode) {
Console.WriteLine("Didn't get a valid response from Honeycomb");
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use _options.TracesEndpoint instead of hardcoding.

Suggested change
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://api.honeycomb.io/1/auth");
httpClient.DefaultRequestHeaders.Add("X-Honeycomb-Team", _options.ApiKey);
var response = httpClient.GetAsync("").GetAwaiter().GetResult();
if (!response.IsSuccessStatusCode) {
Console.WriteLine("Didn't get a valid response from Honeycomb");
return;
}
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(_options.TracesEndpoint);
httpClient.DefaultRequestHeaders.Add("X-Honeycomb-Team", _options.ApiKey);
var response = httpClient.GetAsync("1/auth").GetAwaiter().GetResult();
if (!response.IsSuccessStatusCode) {
Console.WriteLine("Didn't get a valid response from Honeycomb");
return;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that someone can override the traces endpoint, is that an issue? My worry would be people overriding that, and not having the auth API... maybe I'm being a little over cautious?

var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseString);
_environmentSlug = authResponse.Environment.Slug;
_teamSlug = authResponse.Team.Slug;
if (string.IsNullOrEmpty(_environmentSlug) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can generate trace links for classic datasets, it just needs to omit the environment segments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we detect if it's a classic dataset?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we detect if it's a classic dataset?

Classic (nee Legacy) API key is 32 characters:

{
if (string.IsNullOrEmpty(activity.ParentId))
{
Console.WriteLine($"Trace Emitted for {activity.DisplayName}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to expand this in the future to write to an ILogger instead of directly to console. Then users can select where they want these to appear.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually don't want it going through the logging framework, as that could take it into other pipelines, and this is specifically for local development.

@@ -16,6 +16,7 @@ internal class EnvironmentOptions
private const string SampleRateKey = "HONEYCOMB_SAMPLE_RATE";
private const string ServiceNameKey = "SERVICE_NAME";
private const string ServiceVersionKey = "SERVICE_VERSION";
private const string WriteTraceLinksToConsoleKey = "WRITE_TRACE_LINKS_TO_CONSOLE";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels long, maybe LOG_TRACE_LINKS. If we do go along the route of using an ILogger in the future, these trace links may go elsewhere.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using Console is more universal I think, and also keeps it specific to enviroments where you have access to the console.

Copy link
Contributor

@vreynolds vreynolds Jul 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in zoom convo, we thought ENABLE_LOCAL_VISUALIZATIONS would work nicely, as a more generic "mode" setting that is not too specific with what we're writing where

/// <summary>
/// Write links to honeycomb traces as they come in
/// </summary>
public bool WriteTraceLinksToConsole
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should match env var above.

@@ -193,7 +203,8 @@ public string MetricsEndpoint
/// (Optional) Options delegate to configure StackExchange.Redis instrumentation.
/// </summary>
public Action<StackExchangeRedisCallsInstrumentationOptions>
ConfigureStackExchangeRedisClientInstrumentationOptions { get; set; }
ConfigureStackExchangeRedisClientInstrumentationOptions
{ get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this intentional, or did your IDE move this?

@@ -220,6 +231,7 @@ public string MetricsEndpoint
{ "--honeycomb-traces-endpoint", "tracesendpoint" },
{ "--honeycomb-metrics-endpoint", "metricsendpoint" },
{ "--honeycomb-samplerate", "samplerate" },
{ "--honeycomb-write-trace-links-to-console", "writetracelinkstoconsole" },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should match env var

@MikeGoldsmith MikeGoldsmith added type: enhancement New feature or request version: bump minor A PR that adds behavior, but is backwards-compatible. labels Jul 25, 2022
Copy link
Member Author

@martinjt martinjt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed feedback

@@ -16,6 +16,7 @@ internal class EnvironmentOptions
private const string SampleRateKey = "HONEYCOMB_SAMPLE_RATE";
private const string ServiceNameKey = "SERVICE_NAME";
private const string ServiceVersionKey = "SERVICE_VERSION";
private const string WriteTraceLinksToConsoleKey = "WRITE_TRACE_LINKS_TO_CONSOLE";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using Console is more universal I think, and also keeps it specific to enviroments where you have access to the console.

public class ConsoleLinkExporter : BaseExporter<Activity>
{
private readonly HoneycombOptions _options;
private string _teamSlug;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we should be calling out via http as part of startup in any other environment than production. It's an async call in a sync method, and I don't like it all. As it stands, because it's in the ConsoleLinkExporter, it won't be triggered unless you ask for tracelinks.

I think that's probably an internal decision too, so we can refactor that as and when we need it.

Comment on lines 36 to 44
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://api.honeycomb.io/1/auth");
httpClient.DefaultRequestHeaders.Add("X-Honeycomb-Team", _options.ApiKey);

var response = httpClient.GetAsync("").GetAwaiter().GetResult();
if (!response.IsSuccessStatusCode) {
Console.WriteLine("Didn't get a valid response from Honeycomb");
return;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that someone can override the traces endpoint, is that an issue? My worry would be people overriding that, and not having the auth API... maybe I'm being a little over cautious?

var authResponse = JsonSerializer.Deserialize<AuthResponse>(responseString);
_environmentSlug = authResponse.Environment.Slug;
_teamSlug = authResponse.Team.Slug;
if (string.IsNullOrEmpty(_environmentSlug) ||
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we detect if it's a classic dataset?

{
if (string.IsNullOrEmpty(activity.ParentId))
{
Console.WriteLine($"Trace Emitted for {activity.DisplayName}");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually don't want it going through the logging framework, as that could take it into other pipelines, and this is specifically for local development.

Comment on lines 68 to 69
Console.WriteLine($"Trace Emitted for {activity.DisplayName}");
Console.WriteLine($"TraceLink: {GetTraceLink(activity.TraceId.ToString())}");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep in mind that this is just for the root span, not all spans, so even in large volume applications, we should only see a small amount of spans.

1 line is actually pretty long as the url is already quite long, which is why I extended it to 2. Happy to hear thoughts though, I'm not 100% convinced this is right.

private string GetTraceLink(string traceId)
{
var dataset = _options.ServiceName;
return $"http://ui.honeycomb.io/{_teamSlug}/environments/{_environmentSlug}/datasets/{dataset}/trace?trace_id={traceId}";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I have a way of knowing that it's a classic just from the ApiKey or settings, then I'm happy to add it.

@martinjt martinjt marked this pull request as ready for review July 26, 2022 11:05
@martinjt martinjt requested a review from a team July 26, 2022 11:05
Copy link
Contributor

@MikeGoldsmith MikeGoldsmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good - thanks @martinjt 👍🏻

I'm happy with the changes to external API (env var / settings.json) and what it outputs. The remainder is internal logic and can be iterated on as we wish.

@vreynolds
Copy link
Contributor

Circle is being weird, but I triggered the run by creating another branch off this PR.

There's an error trying to run tests:
C:\Program Files\dotnet\sdk\5.0.406\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.TargetFrameworkInference.targets(141,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 6.0. Either target .NET 5.0 or lower, or use a version of the .NET SDK that supports .NET 6.0. [C:\Users\circleci\project\test\Honeycomb.OpenTelemetry.Tests\Honeycomb.OpenTelemetry.Tests.csproj]

@vreynolds
Copy link
Contributor

Looks like Circle may not have .NET 6 in their orb

@cartermp
Copy link
Member

STILL?! ugh

@martinjt
Copy link
Member Author

martinjt commented Jul 28, 2022 via email

@cartermp
Copy link
Member

It's quite reasonable to do that, though. Blegh. It also means having a minimal apis example can't be done yet either. :shakes-fist-at-circle-ci:

@martinjt
Copy link
Member Author

I've added an install step to the CI build @cartermp , happy to revert to .NET 5 if we need to though. I can't trigger the build, so let me know if it works.

@martinjt
Copy link
Member Author

Reverted this to .NET 5.0 as it was only the test project at this stage.

I'll investigate separately how we add .NET 6.0 inside the pipeline.

@MikeGoldsmith MikeGoldsmith merged commit 5730151 into honeycombio:main Aug 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement New feature or request version: bump minor A PR that adds behavior, but is backwards-compatible.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants