bcuff / dd-trace-csharp

A C# Implementation of Data Dog Tracing
Apache License 2.0
3 stars 6 forks source link

Entity Framework #1

Open CumpsD opened 6 years ago

CumpsD commented 6 years ago

Nice library, thanks!

How would you use this with Entity Framework?

bcuff commented 6 years ago

It's been a while since i've used entity framework. I assume it uses ADO .NET under the hood. If that's the case and it offers a way to provide an alternative DbConnection object then you could inject a TraceDbConnection instead.

CumpsD commented 6 years ago

I've tried this, but apparently it needs a DbConnection and TraceDbConnection is an IDbConnection

                services
                    .AddDbContextPool<BackofficeContext>(options => options
                        .UseLoggerFactory(loggerFactory)
                        .UseSqlServer(new TraceDbConnection(new SqlConnection(backofficeProjectionsConnectionString)), sqlServerOptions =>
                        {
                            sqlServerOptions.EnableRetryOnFailure();
                            sqlServerOptions.MigrationsHistoryTable(MigrationTables.Backoffice, Schema.Backoffice);
                        }));
CumpsD commented 6 years ago

Maybe this can help you:

namespace DataDog.Tracing.Sql
{
    using System;
    using System.Data;
    using System.Data.Common;

    public class TraceDbConnection : DbConnection
    {
        private const string ServiceName = "sql";

        readonly ISpanSource _spanSource;
        readonly DbConnection _connection;

        public TraceDbConnection(DbConnection connection)
            : this(connection, TraceContextSpanSource.Instance) { }

        public TraceDbConnection(DbConnection connection, ISpanSource spanSource)
        {
            _connection = connection ?? throw new ArgumentNullException(nameof(connection));
            _spanSource = spanSource ?? throw new ArgumentNullException(nameof(spanSource));
        }

        public IDbConnection InnerConnection => _connection;

        public new void Dispose() => _connection.Dispose();

        protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => _connection.BeginTransaction(isolationLevel);

        protected override DbCommand CreateDbCommand() => new TraceDbCommand(_connection.CreateCommand(), _spanSource);

        public override void ChangeDatabase(string databaseName) => _connection.ChangeDatabase(databaseName);

        public override void Close() => _connection.Close();

        public override void Open()
        {
            var span = _spanSource.Begin("sql.connect", ServiceName, _connection.Database, ServiceName);
            try
            {
                _connection.Open();
            }
            catch (Exception ex)
            {
                span?.SetError(ex);
                throw;
            }
            finally
            {
                span?.Dispose();
            }
        }

        public override string ConnectionString
        {
            get => _connection.ConnectionString;
            set => _connection.ConnectionString = value;
        }

        public override int ConnectionTimeout => _connection.ConnectionTimeout;

        public override string Database => _connection.Database;

        public override string DataSource => _connection.DataSource;

        public override string ServerVersion => _connection.ServerVersion;

        public override ConnectionState State => _connection.State;
    }

    public class TraceDbCommand : DbCommand
    {
        private const string ServiceName = "sql";
        private readonly DbCommand _command;
        private readonly ISpanSource _spanSource;

        public TraceDbCommand(DbCommand command)
            : this(command, TraceContextSpanSource.Instance) { }

        public TraceDbCommand(DbCommand command, ISpanSource spanSource)
        {
            _command = command;
            _spanSource = spanSource;
        }

        public IDbCommand InnerCommand => _command;

        public new void Dispose() => _command.Dispose();

        public override void Cancel() => _command.Cancel();

        protected override DbParameter CreateDbParameter() => _command.CreateParameter();

        protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
        {
            const string name = "sql." + nameof(ExecuteReader);
            var span = _spanSource.Begin(name, ServiceName, _command.Connection.Database, ServiceName);
            try
            {
                if (span != null)
                {
                    const string metaKey = "sql." + nameof(CommandBehavior);
                    span.SetMeta(metaKey, behavior.ToString("x"));
                    SetMeta(span);
                }
                return _command.ExecuteReader(behavior);
            }
            catch (Exception ex)
            {
                span?.SetError(ex);
                throw;
            }
            finally
            {
                span?.Dispose();
            }
        }

        private void SetMeta(ISpan span)
        {
            span.SetMeta("sql.CommandText", CommandText);
            span.SetMeta("sql.CommandType", CommandType.ToString());
        }

        public override int ExecuteNonQuery()
        {
            const string name = "sql." + nameof(ExecuteNonQuery);
            var span = _spanSource.Begin(name, ServiceName, _command.Connection.Database, ServiceName);
            try
            {
                var result = _command.ExecuteNonQuery();
                if (span != null)
                {
                    span.SetMeta("sql.RowsAffected", result.ToString());
                    SetMeta(span);
                }
                return result;
            }
            catch (Exception ex)
            {
                span?.SetError(ex);
                throw;
            }
            finally
            {
                span?.Dispose();
            }
        }

        public override object ExecuteScalar()
        {
            const string name = "sql." + nameof(ExecuteScalar);
            var span = _spanSource.Begin(name, ServiceName, _command.Connection.Database, ServiceName);
            try
            {
                if (span != null) SetMeta(span);
                return _command.ExecuteScalar();
            }
            catch (Exception ex)
            {
                span?.SetError(ex);
                throw;
            }
            finally
            {
                span?.Dispose();
            }
        }

        public override void Prepare() => _command.Prepare();

        protected override DbParameterCollection DbParameterCollection => _command.Parameters;

        public override bool DesignTimeVisible { get; set; }

        public override string CommandText
        {
            get => _command.CommandText;
            set => _command.CommandText = value;
        }

        public override int CommandTimeout
        {
            get => _command.CommandTimeout;
            set => _command.CommandTimeout = value;
        }

        public override CommandType CommandType
        {
            get => _command.CommandType;
            set => _command.CommandType = value;
        }

        protected override DbConnection DbConnection
        {
            get => _command.Connection;
            set => _command.Connection = value;
        }

        protected override DbTransaction DbTransaction
        {
            get => _command.Transaction;
            set => _command.Transaction = value;
        }

        public override UpdateRowSource UpdatedRowSource
        {
            get => _command.UpdatedRowSource;
            set => _command.UpdatedRowSource = value;
        }
    }
}
CumpsD commented 6 years ago

@bcuff I can confirm with the above code I can trace EF :)

Perhaps it is something which can be added?

bcuff commented 6 years ago

@CumpsD sure. Feel free to submit that as a pull request.

bcuff commented 6 years ago

I invited you to the repository so can push your changes to a new branch without having to fork.

CumpsD commented 6 years ago

There you go: https://github.com/bcuff/dd-trace-csharp/pull/4

I also added 3 smaller PRs