Example #1
0
        /// <summary>
        /// Logs all name/value pairs as a single collection using concurrent MERGE statements.
        /// </summary>
        /// <param name="conn"></param>
        /// <param name="exCollectionID"></param>
        /// <param name="coll"></param>
        /// <returns></returns>
        async Task LogCollection(SqlConnectionContext conn, SHA1Hash exCollectionID, IDictionary<string, string> coll)
        {
            // The exCollectionID should be pre-calculated by `CalcCollectionID`.

            // Check if the exCollectionID exists already:
            int? collectionCount = await conn.ExecReader(
@"SELECT COUNT(exCollectionID) FROM [dbo].[exCollectionKeyValue] WHERE [exCollectionID] = @exCollectionID",
                prms =>
                    prms.AddInParamSHA1("@exCollectionID", exCollectionID),
                    async dr => await dr.ReadAsync() ? dr.GetInt32(0) : (int?)null
            );

            // Don't bother logging name-value pairs if the collection already exists:
            if (!collectionCount.HasValue) return;
            if (collectionCount.Value == coll.Count) return;

            const int numTasksPerPair = 2;

            // Create an array of tasks to wait upon:
            var tasks = new Task[coll.Count * numTasksPerPair];

            // Fill out the array of tasks with concurrent MERGE statements for each name/value pair:
            using (var en = coll.Keys.GetEnumerator())
                for (int i = 0; en.MoveNext(); ++i)
                {
                    string name = en.Current;
                    string value = coll[name];

                    var exCollectionValueID = Hash.SHA1(value);

                    // Merge the Value record:
                    tasks[i * numTasksPerPair + 0] = conn.ExecNonQuery(
    @"MERGE [dbo].[exCollectionValue] WITH (HOLDLOCK) AS target
USING (SELECT @exCollectionValueID) AS source (exCollectionValueID)
ON (target.exCollectionValueID = source.exCollectionValueID)
WHEN NOT MATCHED THEN
    INSERT ([exCollectionValueID], [Value])
    VALUES (@exCollectionValueID,  @Value );",
                        prms =>
                            prms.AddInParamSHA1("@exCollectionValueID", exCollectionValueID)
                                .AddInParamSize("@Value", SqlDbType.VarChar, -1, value)
                    );

                    // Merge the Name-Value record:
                    tasks[i * numTasksPerPair + 1] = conn.ExecNonQuery(
    @"MERGE [dbo].[exCollectionKeyValue] WITH (HOLDLOCK) AS target
USING (SELECT @exCollectionID, @Name, @exCollectionValueID) AS source (exCollectionID, Name, exCollectionValueID)
ON (target.exCollectionID = source.exCollectionID AND target.Name = source.Name AND target.exCollectionValueID = source.exCollectionValueID)
WHEN NOT MATCHED THEN
    INSERT ([exCollectionID], [Name], [exCollectionValueID])
    VALUES (@exCollectionID,  @Name,  @exCollectionValueID );",
                        prms =>
                            prms.AddInParamSHA1("@exCollectionID", exCollectionID)
                                .AddInParamSize("@Name", SqlDbType.VarChar, 96, name)
                                .AddInParamSHA1("@exCollectionValueID", exCollectionValueID)
                    );
                }

            // Our final task's completion depends on all the tasks created thus far:
            await Task.WhenAll(tasks);
        }
Example #2
0
        async Task<HashedLogIdentifier> LogExceptionRecursively(SqlConnectionContext conn, ExceptionWithCapturedContext ctx, int? parentInstanceID = null)
        {
            // Create the exTargetSite if it does not exist:
            var ts = ctx.TargetSite;
            SHA1Hash? exTargetSiteID = null;
            Task tskTargetSite = null;
            if (ts != null)
            {
                exTargetSiteID = ts.TargetSiteID;

                tskTargetSite = conn.ExecNonQuery(
@"MERGE [dbo].[exTargetSite] WITH (HOLDLOCK) AS target
USING (SELECT @exTargetSiteID) AS source (exTargetSiteID)
ON (target.exTargetSiteID = source.exTargetSiteID)
WHEN NOT MATCHED THEN
    INSERT ([exTargetSiteID], [AssemblyName], [TypeName], [MethodName], [ILOffset], [FileName], [FileLineNumber], [FileColumnNumber])
    VALUES (@exTargetSiteID,  @AssemblyName,  @TypeName , @MethodName , @ILOffset , @FileName , @FileLineNumber , @FileColumnNumber );",
                    prms =>
                        prms.AddInParamSHA1("@exTargetSiteID", exTargetSiteID.GetValueOrDefault())
                            .AddInParamSize("@AssemblyName", SqlDbType.NVarChar, 256, ts.AssemblyName)
                            .AddInParamSize("@TypeName", SqlDbType.NVarChar, 256, ts.TypeName)
                            .AddInParamSize("@MethodName", SqlDbType.NVarChar, 256, ts.MethodName)
                            .AddInParam("@ILOffset", SqlDbType.Int, ts.ILOffset)
                            .AddInParamSize("@FileName", SqlDbType.NVarChar, 256, ts.FileName)
                            .AddInParam("@FileLineNumber", SqlDbType.Int, ts.FileLineNumber)
                            .AddInParam("@FileColumnNumber", SqlDbType.Int, ts.FileColumnNumber)
                );
            }

            SHA1Hash? userStateCollectionID = null;
            if (ctx.UserState != null)
            {
                userStateCollectionID = CalcCollectionID(ctx.UserState);
            }

            // Create the exException record if it does not exist:
            var tskGetPolicy = conn.ExecReader(
@"MERGE [dbo].[exException] WITH (HOLDLOCK) AS target
USING (SELECT @exExceptionID) AS source (exExceptionID)
ON (target.exExceptionID = source.exExceptionID)
WHEN NOT MATCHED THEN
    INSERT ([exExceptionID], [AssemblyName], [TypeName], [StackTrace], [exTargetSiteID])
    VALUES (@exExceptionID,  @AssemblyName,  @TypeName,  @StackTrace,  @exTargetSiteID );

SELECT excpol.[LogWebContext], excpol.[LogWebRequestHeaders]
FROM [dbo].[exExceptionPolicy] excpol WITH (NOLOCK)
WHERE excpol.[exExceptionID] = @exExceptionID;",
                prms =>
                    prms.AddInParamSHA1("@exExceptionID", ctx.ExceptionID)
                        .AddInParamSize("@AssemblyName", SqlDbType.NVarChar, 256, ctx.AssemblyName)
                        .AddInParamSize("@TypeName", SqlDbType.NVarChar, 256, ctx.TypeName)
                        .AddInParamSize("@StackTrace", SqlDbType.NVarChar, -1, ctx.StackTrace)
                        .AddInParamSHA1("@exTargetSiteID", exTargetSiteID),
                // Read the SELECT result set into an ExceptionPolicy, or use the default policy:
                async dr =>
                    !await dr.ReadAsync()
                    ? ExceptionPolicy.Default
                    : new ExceptionPolicy(
                        logWebContext: dr.GetBoolean(dr.GetOrdinal("LogWebContext")),
                        logWebRequestHeaders: dr.GetBoolean(dr.GetOrdinal("LogWebRequestHeaders"))
                    )
            );

            // Create the exException record if it does not exist:
            var exApplicationID = CalcApplicationID(cfg);
            var tskApplication = conn.ExecNonQuery(
@"MERGE [dbo].[exApplication] WITH (HOLDLOCK) AS target
USING (SELECT @exApplicationID) AS source (exApplicationID)
ON (target.exApplicationID = source.exApplicationID)
WHEN NOT MATCHED THEN
    INSERT ([exApplicationID], [MachineName], [ApplicationName], [EnvironmentName], [ProcessPath])
    VALUES (@exApplicationID,  @MachineName,  @ApplicationName,  @EnvironmentName,  @ProcessPath );",
                prms =>
                    prms.AddInParamSHA1("@exApplicationID", exApplicationID)
                        .AddInParamSize("@MachineName", SqlDbType.VarChar, 64, cfg.MachineName)
                        .AddInParamSize("@ApplicationName", SqlDbType.VarChar, 96, cfg.ApplicationName)
                        .AddInParamSize("@EnvironmentName", SqlDbType.VarChar, 32, cfg.EnvironmentName)
                        .AddInParamSize("@ProcessPath", SqlDbType.NVarChar, 256, cfg.ProcessPath)
            );

            // Create the instance record:
            var tskInstance = conn.ExecNonQuery(
@"INSERT INTO [dbo].[exInstance]
       ([exExceptionID], [exApplicationID], [LoggedTimeUTC], [SequenceNumber], [IsHandled], [ApplicationIdentity], [ParentInstanceID], [CorrelationID], [ManagedThreadId], [UserStateCollectionID], [Message])
VALUES (@exExceptionID,  @exApplicationID,  @LoggedTimeUTC,  @SequenceNumber,  @IsHandled,  @ApplicationIdentity,  @ParentInstanceID,  @CorrelationID,  @ManagedThreadId,  @UserStateCollectionID,  @Message );
SET @exInstanceID = SCOPE_IDENTITY();",
                prms =>
                    prms.AddOutParam("@exInstanceID", SqlDbType.Int)
                        .AddInParamSHA1("@exExceptionID", ctx.ExceptionID)
                        .AddInParamSHA1("@exApplicationID", exApplicationID)
                        .AddInParam("@LoggedTimeUTC", SqlDbType.DateTime2, ctx.LoggedTimeUTC)
                        .AddInParam("@SequenceNumber", SqlDbType.Int, ctx.SequenceNumber)
                        .AddInParam("@IsHandled", SqlDbType.Bit, ctx.IsHandled)
                        .AddInParamSize("@ApplicationIdentity", SqlDbType.NVarChar, 128, cfg.ApplicationIdentity)
                        .AddInParam("@ParentInstanceID", SqlDbType.Int, parentInstanceID)
                        .AddInParam("@CorrelationID", SqlDbType.UniqueIdentifier, ctx.CorrelationID)
                        .AddInParam("@ManagedThreadId", SqlDbType.Int, ctx.ManagedThreadID)
                        .AddInParamSHA1("@UserStateCollectionID", userStateCollectionID)
                        .AddInParamSize("@Message", SqlDbType.NVarChar, 256, ctx.Exception.Message),
                (prms, rc) =>
                {
                    return (int)prms["@exInstanceID"].Value;
                }
            );

            // Await the exInstance record creation:
            int exInstanceID = await tskInstance;

            // Await the exception policy result:
            var policy = await tskGetPolicy;

            // If logging the web context is enabled and we have a web context to work with, log it:
            Task tskLoggingWeb = null;
            if (policy.LogWebContext && ctx.CapturedHttpContext != null)
            {
                tskLoggingWeb = LogWebContext(conn, policy, ctx, exInstanceID);
            }

            // Log the UserState collection:
            if (userStateCollectionID != null)
                await LogCollection(conn, userStateCollectionID.Value, ctx.UserState);

            // Wait for any outstanding logging tasks:
            if (tskLoggingWeb != null) await tskLoggingWeb;
            if (tskTargetSite != null) await tskTargetSite;
            await tskApplication;

            // Recursively log inner exceptions:
            var inner = ctx.InnerException;
            if (inner != null)
                // Return the inner-most exInstanceID because you can easily drill down through the ParentInstanceID columns to reach the root level.
                return await LogExceptionRecursively(conn, inner, exInstanceID);

            return new HashedLogIdentifier(ctx.ExceptionID, exInstanceID);
        }