fix(observability): preserve backend release identity (#114)

This commit is contained in:
Alexandre Brandizzi 2026-09-15 16:08:14 -03:00 • committed by GitHub
parent 67089c2135
commit 3a4af64a64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 108 additions and 13 deletions

View file

@ -1,3 +1,4 @@
using System.Reflection;
using System.Security.Claims; using System.Security.Claims;
using Api.SeaHavenIndustries.Observability; using Api.SeaHavenIndustries.Observability;
using FluentAssertions; using FluentAssertions;
@ -14,6 +15,7 @@ namespace Api.SeaHavenIndustries.Tests;
public class SentryObservabilityTests public class SentryObservabilityTests
{ {
private static readonly string ValidSha = new string('a', 40); private static readonly string ValidSha = new string('a', 40);
private static readonly string UpperSha = "ABCDEF0123456789ABCDEF0123456789ABCDEF01";
[Fact] [Fact]
public void ResolveRelease_Accepts_ServicePrefixed40HexCommit() public void ResolveRelease_Accepts_ServicePrefixed40HexCommit()
@ -23,15 +25,57 @@ public class SentryObservabilityTests
} }
[Theory] [Theory]
[InlineData(null)]
[InlineData("1.0.0")] [InlineData("1.0.0")]
[InlineData("shoc-backend@abc")] [InlineData("shoc-backend@abc")]
[InlineData("shoc-backend@not-a-sha-at-all")] [InlineData("shoc-backend@not-a-sha-at-all")]
[InlineData("other-service@" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")] [InlineData("other-service@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")]
[InlineData("SHOC-BACKEND@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")]
[InlineData("")] [InlineData("")]
public void ResolveRelease_FallsBackToLocalDevelopment_ForNonReleaseVersion(string version) [InlineData("shoc-backend@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")]
[InlineData("shoc-backend@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")]
[InlineData("shoc-backend@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")]
[InlineData("shoc-backend@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+dirty")]
[InlineData("shoc-backend@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+")]
[InlineData("shoc-backend@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa++")]
[InlineData("shoc-backend@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+extra")]
public void ResolveRelease_FallsBackToLocalDevelopment_ForNonReleaseVersion(string? version)
{ {
SentryObservability.ResolveRelease(version) SentryObservability.ResolveRelease(version)
.Should().Be(SentryObservability.LocalDevelopmentRelease); .Should().Be(SentryObservability.LocalDevelopmentRelease);
SentryObservability.ResolveCommitSha(version)
.Should().BeNull();
}
[Fact]
public void ResolveRelease_Accepts_SdkAppendedMatchingSourceRevisionSuffix()
{
var informationalVersion = "shoc-backend@" + ValidSha + "+" + ValidSha;
SentryObservability.ResolveRelease(informationalVersion)
.Should().Be("shoc-backend@" + ValidSha);
SentryObservability.ResolveCommitSha(informationalVersion)
.Should().Be(ValidSha);
}
[Fact]
public void ResolveRelease_Accepts_SuffixDifferingOnlyInCase_FromTheCommitSha()
{
var canonicalSha = UpperSha.ToLowerInvariant();
SentryObservability.ResolveRelease("shoc-backend@" + UpperSha + "+" + canonicalSha)
.Should().Be("shoc-backend@" + canonicalSha);
}
[Fact]
public void ResolveRelease_NormalizesUppercaseCommitShaToLowercase()
{
var canonicalSha = UpperSha.ToLowerInvariant();
SentryObservability.ResolveRelease("shoc-backend@" + UpperSha)
.Should().Be("shoc-backend@" + canonicalSha);
SentryObservability.ResolveCommitSha("shoc-backend@" + UpperSha)
.Should().Be(canonicalSha);
} }
[Fact] [Fact]
@ -39,6 +83,28 @@ public class SentryObservabilityTests
{ {
var release = SentryObservability.ResolveRelease("shoc-backend@" + ValidSha); var release = SentryObservability.ResolveRelease("shoc-backend@" + ValidSha);
release[(SentryObservability.ServiceName.Length + 1)..].Should().Be(ValidSha); release[(SentryObservability.ServiceName.Length + 1)..].Should().Be(ValidSha);
SentryObservability.ResolveCommitSha("shoc-backend@" + ValidSha)
.Should().Be(ValidSha);
}
[Fact]
public void ResolveRelease_AndCommitSha_FromNonReleaseAssembly_FallBackSafely()
{
var assembly = typeof(SentryObservabilityTests).Assembly;
SentryObservability.ResolveRelease(assembly)
.Should().Be(SentryObservability.LocalDevelopmentRelease);
SentryObservability.ResolveCommitSha(assembly)
.Should().BeNull();
}
[Fact]
public void ResolveRelease_AndCommitSha_FromNullAssembly_FallBackSafely()
{
SentryObservability.ResolveRelease((Assembly?)null)
.Should().Be(SentryObservability.LocalDevelopmentRelease);
SentryObservability.ResolveCommitSha((Assembly?)null)
.Should().BeNull();
} }
[Fact] [Fact]

View file

@ -29,21 +29,46 @@ public static class SentryObservability
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()? .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
.InformationalVersion; .InformationalVersion;
return IsReleaseInformationalVersion(informationalVersion) return ResolveCommitSha(informationalVersion);
? informationalVersion![ReleasePrefix.Length..]
: null;
} }
public static string ResolveRelease(string? informationalVersion) => public static string ResolveRelease(string? informationalVersion) =>
IsReleaseInformationalVersion(informationalVersion) TryResolveCommitSha(informationalVersion, out var commitSha)
? informationalVersion! ? ReleasePrefix + commitSha
: LocalDevelopmentRelease; : LocalDevelopmentRelease;
public static string? ResolveCommitSha(string? informationalVersion) =>
TryResolveCommitSha(informationalVersion, out var commitSha)
? commitSha
: null;
public static bool IsReleaseInformationalVersion(string? value) => public static bool IsReleaseInformationalVersion(string? value) =>
value is not null TryResolveCommitSha(value, out _);
&& value.StartsWith(ReleasePrefix, StringComparison.Ordinal)
&& value.Length == ReleasePrefix.Length + CommitShaLength private static bool TryResolveCommitSha(string? value, out string? commitSha)
&& value[ReleasePrefix.Length..].All(c => c is >= '0' and <= '9' or >= 'a' and <= 'f' or >= 'A' and <= 'F'); {
commitSha = null;
if (value is null || !value.StartsWith(ReleasePrefix, StringComparison.Ordinal))
return false;
var body = value[ReleasePrefix.Length..];
var suffixSeparatorIndex = body.IndexOf('+');
var sha = suffixSeparatorIndex < 0 ? body : body[..suffixSeparatorIndex];
if (sha.Length != CommitShaLength || !sha.All(IsCommitShaHexDigit))
return false;
if (suffixSeparatorIndex >= 0
&& !string.Equals(body[(suffixSeparatorIndex + 1)..], sha, StringComparison.OrdinalIgnoreCase))
return false;
commitSha = sha.ToLowerInvariant();
return true;
}
private static bool IsCommitShaHexDigit(char c) =>
c is >= '0' and <= '9' or >= 'a' and <= 'f' or >= 'A' and <= 'F';
public static void ConfigureRequest(IHub hub, HttpContext context) public static void ConfigureRequest(IHub hub, HttpContext context)
{ {

View file

@ -14,11 +14,14 @@ route values, query strings, or identity claims.
## Decision ## Decision
1. **Release identity.** `SentryObservability.ResolveRelease(Assembly)` accepts an 1. **Release identity.** `SentryObservability.ResolveRelease(Assembly)` accepts an
informational version shaped `shoc-backend@<40 hex commit sha>` and otherwise informational version shaped `shoc-backend@<40 hex commit sha>`, tolerates the
resolves to `local-development`. `scripts/package-elastic-beanstalk.sh` resolves .NET SDK's matching `+<same sha>` suffix for compatibility, canonicalizes the
result to lowercase, and otherwise resolves to `local-development`.
`scripts/package-elastic-beanstalk.sh` resolves
the commit from `APP_COMMIT_SHA` → `GITHUB_SHA` → `git rev-parse HEAD`, fails the commit from `APP_COMMIT_SHA` → `GITHUB_SHA` → `git rev-parse HEAD`, fails
when `CI=true` cannot produce a valid 40-hex sha, and publishes with when `CI=true` cannot produce a valid 40-hex sha, and publishes with
`-p:SourceRevisionId`, `-p:InformationalVersion=shoc-backend@<sha>`, `-p:SourceRevisionId`, `-p:InformationalVersion=shoc-backend@<sha>`,
`-p:IncludeSourceRevisionInInformationalVersion=false`,
`-p:DebugType=portable`, `-p:DebugSymbols=true`. `-p:DebugType=portable`, `-p:DebugSymbols=true`.
2. **Default tags.** `service=shoc-backend` and `app.commit=<sha | local-development>` 2. **Default tags.** `service=shoc-backend` and `app.commit=<sha | local-development>`
on `SentryOptions.DefaultTags`. All events use the same commit-addressed on `SentryOptions.DefaultTags`. All events use the same commit-addressed

View file

@ -112,6 +112,7 @@ log "publish $API_PROJECT (Release, self-contained, $RUNTIME)"
-p:UseAppHost=true \ -p:UseAppHost=true \
-p:SourceRevisionId="$COMMIT_SHA" \ -p:SourceRevisionId="$COMMIT_SHA" \
-p:InformationalVersion="shoc-backend@$COMMIT_SHA" \ -p:InformationalVersion="shoc-backend@$COMMIT_SHA" \
-p:IncludeSourceRevisionInInformationalVersion=false \
-p:DebugType=portable \ -p:DebugType=portable \
-p:DebugSymbols=true -p:DebugSymbols=true