// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Diagnostics; using System.Diagnostics.Metrics; namespace Microsoft.Teams.Apps.Diagnostics; /// /// Singletons for the Apps-level , , and instruments. /// Internal to Microsoft.Teams.Apps. /// internal static class AppsTelemetry { private const string s_version = ThisAssembly.NuGetPackageVersion; public static readonly ActivitySource Source = new(TeamsBotApplicationTelemetry.ActivitySourceName, s_version); public static readonly Meter Meter = new(TeamsBotApplicationTelemetry.MeterName, s_version); public static readonly Counter HandlerDispatched = Meter.CreateCounter(Metrics.HandlerDispatched, description: "Total handler invocations dispatched by the router."); public static readonly Histogram HandlerDuration = Meter.CreateHistogram(Metrics.HandlerDuration, unit: "ms", description: "Duration of individual handler invocations."); public static readonly Counter HandlerFailures = Meter.CreateCounter(Metrics.HandlerFailures, description: "Total handler invocations that threw an exception."); public static readonly Counter HandlerUnmatched = Meter.CreateCounter(Metrics.HandlerUnmatched, description: "Total activities that found no matching route."); // ── State instruments ──────────────────────────────────────────────── public static readonly Histogram StateLoadDuration = Meter.CreateHistogram(Metrics.StateLoadDuration, unit: "ms", description: "Duration of state load from cache."); public static readonly Histogram StateSaveDuration = Meter.CreateHistogram(Metrics.StateSaveDuration, unit: "ms", description: "Duration of state save to cache."); public static readonly Counter StateCacheErrors = Meter.CreateCounter(Metrics.StateCacheErrors, description: "Total cache operation failures for turn state."); public static readonly Histogram StateBytesRead = Meter.CreateHistogram(Metrics.StateBytesRead, unit: "By", description: "Bytes read from cache per state load."); public static readonly Histogram StateBytesWritten = Meter.CreateHistogram(Metrics.StateBytesWritten, unit: "By", description: "Bytes written to cache per state save."); // ── OAuth instruments ──────────────────────────────────────────────── public static readonly Counter OAuthOperationCount = Meter.CreateCounter(Metrics.OAuthOperations, description: "Total OAuth flow operations attempted. For verify_state and signin_failure invokes, each per-flow attempt is counted independently in multi-connection deployments."); public static readonly Histogram OAuthOperationDuration = Meter.CreateHistogram(Metrics.OAuthOperationDuration, unit: "ms", description: "Duration of OAuth flow operations."); public static readonly Counter OAuthErrors = Meter.CreateCounter(Metrics.OAuthErrors, description: "Total OAuth flow operations that failed with an unexpected exception. Expected protocol fallbacks (HTTP 404/400/412 from the Token Service) are not counted here; they are recorded as oauth.result=failure on teams.oauth.operations instead."); public static class Spans { public const string Handler = "handler"; public const string StateLoad = "state.load"; public const string StateSave = "state.save"; public const string StateDelete = "state.delete"; // OAuth spans public const string OAuthSignIn = "oauth.signin"; public const string OAuthSignOut = "oauth.signout"; public const string OAuthGetToken = "oauth.get_token"; public const string OAuthTokenExchange = "oauth.token_exchange"; public const string OAuthVerifyState = "oauth.verify_state"; public const string OAuthSignInFailure = "oauth.signin_failure"; public const string OAuthConnectionStatus = "oauth.connection_status"; } public static class Tags { public const string HandlerType = "handler.type"; public const string HandlerDispatch = "handler.dispatch"; public const string ActivityType = "activity.type"; public const string InvokeName = "invoke.name"; // State tags public const string StateConversationHit = "state.conversation.hit"; public const string StateUserHit = "state.user.hit"; public const string StateConversationDirty = "state.conversation.dirty"; public const string StateUserDirty = "state.user.dirty"; public const string StateBytesRead = "state.bytes.read"; public const string StateBytesWritten = "state.bytes.written"; public const string Operation = "operation"; // OAuth tags public const string OAuthConnection = "oauth.connection"; public const string OAuthOperation = "oauth.operation"; public const string OAuthResult = "oauth.result"; public const string OAuthErrorType = "oauth.error.type"; public const string OAuthFailureCode = "oauth.failure.code"; public const string OAuthCallbackInvoked = "oauth.callback.invoked"; public const string InvokeResponseStatus = "invoke.response.status"; } public static class Metrics { public const string HandlerDispatched = "teams.handler.dispatched"; public const string HandlerDuration = "teams.handler.duration"; public const string HandlerFailures = "teams.handler.failures"; public const string HandlerUnmatched = "teams.handler.unmatched"; // State metrics public const string StateLoadDuration = "teams.state.load.duration"; public const string StateSaveDuration = "teams.state.save.duration"; public const string StateCacheErrors = "teams.state.cache.errors"; public const string StateBytesRead = "teams.state.bytes.read"; public const string StateBytesWritten = "teams.state.bytes.written"; // OAuth metrics public const string OAuthOperations = "teams.oauth.operations"; public const string OAuthOperationDuration = "teams.oauth.operation.duration"; public const string OAuthErrors = "teams.oauth.errors"; } /// /// Values used for the tag. /// Low cardinality: one of seven well-known operation names. /// public static class OAuthOperations { public const string SignIn = "signin"; public const string SignOut = "signout"; public const string GetToken = "get_token"; public const string TokenExchange = "token_exchange"; public const string VerifyState = "verify_state"; public const string SignInFailure = "signin_failure"; public const string ConnectionStatus = "connection_status"; } /// /// Values used for the tag. /// public static class OAuthResults { /// SignIn returned a cached token without sending an OAuthCard. public const string Cached = "cached"; /// SignIn sent an OAuthCard because no cached token was found. public const string CardSent = "card_sent"; /// GetToken found a cached token in the Token Store. public const string Hit = "hit"; /// GetToken found no cached token in the Token Store. public const string Miss = "miss"; /// Operation completed successfully. public const string Success = "success"; /// Expected protocol failure (e.g., Token Service returned 404/400/412, or null state). public const string Failure = "failure"; /// Duplicate signin/tokenExchange invoke; deduplicated to a 200 no-op. public const string Duplicate = "duplicate"; /// verify_state attempted on a flow whose connection didn't match the code. public const string NoToken = "no_token"; /// signin_failure invoke acknowledged and forwarded to the OnSignInFailure callback. public const string Notified = "notified"; } /// /// Values used for the tag. /// Set only on the counter and on spans when an unexpected exception escapes. /// public static class OAuthErrorTypes { public const string HttpError = "http_error"; public const string InvalidOperation = "invalid_op"; public const string EmptyToken = "empty_token"; } /// /// Names of low-cardinality span events emitted by OAuth flows. /// public static class OAuthEvents { public const string CardSent = "oauth.card.sent"; } /// /// Special value used by operations that span all connections /// (e.g., connection_status returns the status of every registered OAuth connection). /// public const string OAuthAllConnections = "all"; }