Skip to content

Commit 1f84595

Browse files
committed
feat: add telemetry support
1 parent 1e1b481 commit 1f84595

File tree

8 files changed

+122
-21
lines changed

8 files changed

+122
-21
lines changed

Catglobe.CgScript.Common/Catglobe.CgScript.Common.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
</Content>
3636
</ItemGroup>
3737

38+
<ItemGroup>
39+
<PackageReference Include="OpenTelemetry.Api" Version="1.12.0" />
40+
</ItemGroup>
41+
3842
<PropertyGroup>
3943
<DefaultItemExcludes>$(DefaultItemExcludes);node_modules/**</DefaultItemExcludes>
4044
<ProjectRootDir>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '.git/index'))\</ProjectRootDir>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Diagnostics;
2+
3+
namespace Catglobe.CgScript.Common;
4+
5+
/// <summary>
6+
/// Telemetry for CgScript
7+
/// </summary>
8+
public static class CgScriptTelemetry
9+
{
10+
public const string TelemetrySourceName = "Catglobe.CgScript";
11+
/// <summary>
12+
/// ActivitySource for CgScript
13+
/// </summary>
14+
public static ActivitySource Source { get; internal set; } = new(TelemetrySourceName);
15+
16+
}

Catglobe.CgScript.Deployment/Deployer.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using System.Net.Http.Json;
1+
using Catglobe.CgScript.Common;
2+
using Microsoft.Extensions.Options;
3+
using System.Diagnostics;
4+
using System.Net.Http.Json;
25
using System.Text.Json;
36
using System.Text.Json.Serialization;
4-
using Catglobe.CgScript.Common;
5-
using Microsoft.Extensions.Options;
67

78
namespace Catglobe.CgScript.Deployment;
89

@@ -23,6 +24,7 @@ internal partial class Deployer(HttpClient httpClient, IScriptProvider provider,
2324

2425
public async Task Sync(string environmentName, CancellationToken token)
2526
{
27+
using var activity = CgScriptTelemetry.Source.StartActivity();
2628
var current = await provider.GetAll();
2729
var mapAfterFirstSync = await SyncMap(current.Keys.Select(x => new CgScriptReference { ScriptName = x }).ToList(), token);
2830
var maker = new CgScriptMaker(environmentName, current, mapAfterFirstSync);
@@ -41,14 +43,20 @@ public async Task Sync(string environmentName, CancellationToken token)
4143

4244
private async Task<CgDeploymentMap> SyncMap(List<CgScriptReference> scripts, CancellationToken token)
4345
{
44-
var req = await httpClient.PostAsJsonAsync($"SyncMap/{_parentId}", scripts, MapSerializer.Default.ListCgScriptReference, token);
46+
using var activity = CgScriptTelemetry.Source.StartActivity();
47+
var req = await httpClient.PostAsJsonAsync($"SyncMap/{_parentId}", scripts, MapSerializer.Default.ListCgScriptReference, token);
48+
if (!req.IsSuccessStatusCode)
49+
activity?.SetStatus(ActivityStatusCode.Error);
4550
req.EnsureSuccessStatusCode();
4651
return await req.Content.ReadFromJsonAsync(MapSerializer.Default.CgDeploymentMap, token) ?? new();
4752
}
4853

4954
private async Task<CgDeploymentMap> UpdateScripts(List<CgScriptDefinition> scripts, CancellationToken token)
5055
{
51-
var req = await httpClient.PostAsJsonAsync($"UpdateScripts/{_parentId}", scripts, MapSerializer.Default.ListCgScriptDefinition, token);
56+
using var activity = CgScriptTelemetry.Source.StartActivity();
57+
var req = await httpClient.PostAsJsonAsync($"UpdateScripts/{_parentId}", scripts, MapSerializer.Default.ListCgScriptDefinition, token);
58+
if (!req.IsSuccessStatusCode)
59+
activity?.SetStatus(ActivityStatusCode.Error);
5260
req.EnsureSuccessStatusCode();
5361
return await req.Content.ReadFromJsonAsync(MapSerializer.Default.CgDeploymentMap, token) ?? new();
5462
}

Catglobe.CgScript.Deployment/HostExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.Extensions.DependencyInjection;
55
using Microsoft.Extensions.DependencyInjection.Extensions;
66
using Microsoft.Extensions.Options;
7+
using OpenTelemetry.Trace;
78

89
namespace Catglobe.CgScript.Deployment;
910

@@ -49,5 +50,11 @@ private static IServiceCollection AddCommonCgScript(IServiceCollection services)
4950
});
5051
return services;
5152
}
53+
54+
/// <summary>
55+
/// Register the CgScript telemetry source
56+
/// </summary>
57+
public static TracerProviderBuilder AddCgScriptInstrumentation(this TracerProviderBuilder builder) => builder.AddSource(CgScriptTelemetry.TelemetrySourceName);
58+
5259
}
5360

Catglobe.CgScript.Runtime/ApiClientBase.cs

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,78 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using System.Diagnostics;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Net.Http.Json;
34
using System.Text.Json;
45
using System.Text.Json.Serialization.Metadata;
6+
using Catglobe.CgScript.Common;
57
using Microsoft.Extensions.Logging;
68

79
namespace Catglobe.CgScript.Runtime;
810

911
internal abstract class ApiClientBase(HttpClient httpClient, ILogger<ICgScriptApiClient> logger) : ICgScriptApiClient
1012
{
11-
public async Task<ScriptResult<TR>> Execute<TP, TR>(string scriptName, TP parameter, JsonTypeInfo<TP> callJsonTypeInfo, JsonTypeInfo<TR> resultJsonTypeInfo, CancellationToken cancellationToken) =>
12-
await ParseResponse(await httpClient.PostAsync(await GetPath(scriptName), await GetJsonContent(scriptName, parameter, callJsonTypeInfo), cancellationToken).ConfigureAwait(false), resultJsonTypeInfo, cancellationToken);
13+
public async Task<ScriptResult<TR>> Execute<TP, TR>(string scriptName, TP parameter, JsonTypeInfo<TP> callJsonTypeInfo, JsonTypeInfo<TR> resultJsonTypeInfo, CancellationToken cancellationToken)
14+
{
15+
using var activity = CgScriptTelemetry.Source.StartActivity(scriptName);
16+
return await ParseResponse(await httpClient.PostAsync(await GetPath(scriptName), await GetJsonContent(scriptName, parameter, callJsonTypeInfo), cancellationToken).ConfigureAwait(false), resultJsonTypeInfo, cancellationToken);
17+
}
1318

14-
public async Task<ScriptResult<TR>> ExecuteArray<TP, TR>(string scriptName, TP parameter, JsonTypeInfo<TP> callJsonTypeInfo, JsonTypeInfo<TR> resultJsonTypeInfo, CancellationToken cancellationToken) =>
15-
await ParseResponse(await httpClient.PostAsync(await GetPath(scriptName, "?expandParameters=true"), await GetJsonContent(scriptName, parameter, callJsonTypeInfo), cancellationToken).ConfigureAwait(false), resultJsonTypeInfo, cancellationToken);
19+
public async Task<ScriptResult<TR>> ExecuteArray<TP, TR>(string scriptName, TP parameter, JsonTypeInfo<TP> callJsonTypeInfo, JsonTypeInfo<TR> resultJsonTypeInfo, CancellationToken cancellationToken)
20+
{
21+
using var activity = CgScriptTelemetry.Source.StartActivity(scriptName);
22+
return await ParseResponse(await httpClient.PostAsync(await GetPath(scriptName, "?expandParameters=true"), await GetJsonContent(scriptName, parameter, callJsonTypeInfo), cancellationToken).ConfigureAwait(false), resultJsonTypeInfo, cancellationToken);
23+
}
1624

17-
public async Task<ScriptResult<TR>> Execute<TR>(string scriptName, JsonTypeInfo<TR> resultJsonTypeInfo, CancellationToken cancellationToken = default) =>
18-
await ParseResponse(await httpClient.PostAsync(await GetPath(scriptName, "?expandParameters=true"), await GetJsonContent(scriptName, null, (JsonTypeInfo<object>)null!), cancellationToken).ConfigureAwait(false), resultJsonTypeInfo, cancellationToken);
25+
public async Task<ScriptResult<TR>> Execute<TR>(string scriptName, JsonTypeInfo<TR> resultJsonTypeInfo, CancellationToken cancellationToken = default)
26+
{
27+
using var activity = CgScriptTelemetry.Source.StartActivity(scriptName);
28+
return await ParseResponse(await httpClient.PostAsync(await GetPath(scriptName, "?expandParameters=true"), await GetJsonContent(scriptName, null, (JsonTypeInfo<object>)null!), cancellationToken).ConfigureAwait(false), resultJsonTypeInfo, cancellationToken);
29+
}
1930

2031
private async Task<ScriptResult<TR>> ParseResponse<TR>(HttpResponseMessage call, JsonTypeInfo<TR> resultJsonTypeInfo, CancellationToken cancellationToken)
2132
{
2233
var jsonTypeInfo = JsonMetadataServices.CreateValueInfo<ScriptResult<TR>>(new(){TypeInfoResolver = new DummyResolver<TR>(resultJsonTypeInfo)}, new ScriptResultConverterWithTypeInfo<TR>(resultJsonTypeInfo));
2334
//if not successful, log the error the server sent
2435
if (!call.IsSuccessStatusCode)
2536
{
37+
Activity.Current?.SetStatus(ActivityStatusCode.Error);
2638
logger.LogInformation(await call.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false));
2739
call.EnsureSuccessStatusCode();
2840
}
2941
var result = await call.Content.ReadFromJsonAsync(jsonTypeInfo, cancellationToken).ConfigureAwait(false);
42+
if (result?.Error is not null)
43+
{
44+
Activity.Current?.SetStatus(ActivityStatusCode.Error);
45+
}
3046
return result ?? throw new IOException("Could not deserialize result");
3147
}
3248

3349
[RequiresUnreferencedCode("JSON")]
34-
public async Task<ScriptResult<TR>> Execute<TP, TR>(string scriptName, TP parameter, JsonSerializerOptions? options, CancellationToken cancellationToken = default) =>
35-
await ParseResponse<TR>(await httpClient.PostAsync(await GetPath(scriptName), await GetJsonContent(scriptName, parameter, options), cancellationToken).ConfigureAwait(false), options, cancellationToken);
50+
public async Task<ScriptResult<TR>> Execute<TP, TR>(string scriptName, TP parameter, JsonSerializerOptions? options, CancellationToken cancellationToken = default)
51+
{
52+
using var activity = CgScriptTelemetry.Source.StartActivity(scriptName);
53+
return await ParseResponse<TR>(await httpClient.PostAsync(await GetPath(scriptName), await GetJsonContent(scriptName, parameter, options), cancellationToken).ConfigureAwait(false), options, cancellationToken);
54+
}
55+
3656
[RequiresUnreferencedCode("JSON")]
37-
public async Task<ScriptResult<TR>> ExecuteArray<TP, TR>(string scriptName, TP parameter, JsonSerializerOptions? options, CancellationToken cancellationToken = default) =>
38-
await ParseResponse<TR>(await httpClient.PostAsync(await GetPath(scriptName, "?expandParameters=true"), await GetJsonContent(scriptName, parameter, options), cancellationToken).ConfigureAwait(false), options, cancellationToken);
57+
public async Task<ScriptResult<TR>> ExecuteArray<TP, TR>(string scriptName, TP parameter, JsonSerializerOptions? options, CancellationToken cancellationToken = default)
58+
{
59+
using var activity = CgScriptTelemetry.Source.StartActivity(scriptName);
60+
return await ParseResponse<TR>(await httpClient.PostAsync(await GetPath(scriptName, "?expandParameters=true"), await GetJsonContent(scriptName, parameter, options), cancellationToken).ConfigureAwait(false), options, cancellationToken);
61+
}
3962

4063
[RequiresUnreferencedCode("JSON")]
41-
public async Task<ScriptResult<TR>> Execute<TR>(string scriptName, IReadOnlyCollection<object> parameters, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) =>
42-
await ParseResponse<TR>(await httpClient.PostAsync(await GetPath(scriptName, "?expandParameters=true"), await GetJsonContent(scriptName, parameters, options), cancellationToken).ConfigureAwait(false), options, cancellationToken);
64+
public async Task<ScriptResult<TR>> Execute<TR>(string scriptName, IReadOnlyCollection<object> parameters, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
65+
{
66+
using var activity = CgScriptTelemetry.Source.StartActivity(scriptName);
67+
return await ParseResponse<TR>(await httpClient.PostAsync(await GetPath(scriptName, "?expandParameters=true"), await GetJsonContent(scriptName, parameters, options), cancellationToken).ConfigureAwait(false), options, cancellationToken);
68+
}
4369

4470
[RequiresUnreferencedCode("JSON")]
45-
public async Task<ScriptResult<TR>> Execute<TR>(string scriptName, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) =>
46-
await ParseResponse<TR>(await httpClient.PostAsync(await GetPath(scriptName), await GetJsonContent(scriptName, null, (JsonTypeInfo<object>)null!), cancellationToken).ConfigureAwait(false), options, cancellationToken);
71+
public async Task<ScriptResult<TR>> Execute<TR>(string scriptName, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
72+
{
73+
using var activity = CgScriptTelemetry.Source.StartActivity(scriptName);
74+
return await ParseResponse<TR>(await httpClient.PostAsync(await GetPath(scriptName), await GetJsonContent(scriptName, null, (JsonTypeInfo<object>)null!), cancellationToken).ConfigureAwait(false), options, cancellationToken);
75+
}
4776

4877
[RequiresUnreferencedCode("JSON")]
4978
private async Task<ScriptResult<TR>> ParseResponse<TR>(HttpResponseMessage call, JsonSerializerOptions? options, CancellationToken cancellationToken)
@@ -52,10 +81,15 @@ private async Task<ScriptResult<TR>> ParseResponse<TR>(HttpResponseMessage call,
5281
//if not successful, log the error the server sent
5382
if (!call.IsSuccessStatusCode)
5483
{
84+
Activity.Current?.SetStatus(ActivityStatusCode.Error);
5585
logger.LogInformation(await call.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false));
5686
call.EnsureSuccessStatusCode();
5787
}
5888
var result = (ScriptResult<TR>?)await call.Content.ReadFromJsonAsync(typeof(ScriptResult<TR>), retOptions, cancellationToken).ConfigureAwait(false);
89+
if (result?.Error is not null)
90+
{
91+
Activity.Current?.SetStatus(ActivityStatusCode.Error);
92+
}
5993
return result ?? throw new IOException("Could not deserialize result");
6094
}
6195

Catglobe.CgScript.Runtime/HostExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.Extensions.Configuration;
44
using Microsoft.Extensions.DependencyInjection;
55
using Microsoft.Extensions.Options;
6+
using OpenTelemetry.Trace;
67

78
namespace Catglobe.CgScript.Runtime;
89

@@ -47,5 +48,10 @@ private static IServiceCollection AddCommonCgScript(IServiceCollection services,
4748
.AddHttpMessageHandler<CgScriptAuthHandler>();
4849
return services;
4950
}
51+
52+
/// <summary>
53+
/// Register the CgScript telemetry source
54+
/// </summary>
55+
public static TracerProviderBuilder AddCgScriptInstrumentation(this TracerProviderBuilder builder) => builder.AddSource(CgScriptTelemetry.TelemetrySourceName);
5056
}
5157

Catglobe.CgScript.Runtime/ScriptMapping.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using System.Diagnostics;
22
using System.Net.Http.Json;
33
using System.Text.Json;
44
using System.Text.Json.Serialization;
@@ -16,7 +16,10 @@ internal partial class ScriptMapping(HttpClient httpClient, IOptions<CgScriptOpt
1616
public async ValueTask EnsureDownloaded()
1717
{
1818
if (_map is not null) return;
19+
using var activity = CgScriptTelemetry.Source.StartActivity("DownloadMap");
1920
var req = await httpClient.GetAsync($"GetMap/{options.Value.FolderResourceId}");
21+
if (!req.IsSuccessStatusCode)
22+
activity?.SetStatus(ActivityStatusCode.Error);
2023
req.EnsureSuccessStatusCode();
2124
_map = await req.Content.ReadFromJsonAsync(MapSerializer.Default.DeploymentMap) ?? new();
2225
}

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,29 @@ In production, the impersonation accounts there can then be set up to have the c
370370

371371
Setup `deployment` and sync your scripts to the Catglobe site.
372372

373+
# Telemetry and OpenTelemetry integration
374+
375+
To enable distributed tracing and telemetry for CgScript operations, you can use the provided `AddCgScriptInstrumentation` extension method. This will register the CgScript telemetry source with OpenTelemetry, allowing you to collect traces for script execution and deployment flows.
376+
377+
### Example: Setting up OpenTelemetry with CgScript
378+
```
379+
using Catglobe.CgScript.Common;
380+
using Catglobe.CgScript.Deployment;
381+
using OpenTelemetry.Trace;
382+
383+
var builder = WebApplication.CreateBuilder(args);
384+
385+
builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>
386+
{
387+
tracerProviderBuilder
388+
.AddAspNetCoreInstrumentation()
389+
.AddHttpClientInstrumentation()
390+
.AddCgScriptInstrumentation() // Registers CgScript telemetry source
391+
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyApp"));
392+
});
393+
```
394+
You can now view CgScript-related activities in your OpenTelemetry-compatible backend (such as Jaeger, Zipkin, or Azure Monitor).
395+
373396
# FAQ
374397

375398
## File name mapping to security

0 commit comments

Comments
 (0)