Przejdź do treści

DevStyle - Strona Główna
object.LogInfo() – do logowania tożsamości obiektu

object.LogInfo() – do logowania tożsamości obiektu

Maciej Aniserowicz

2 marca 2011

Backend

Niedawno przedstawiłem TokenContext, klasę pomocniczą upraszczającą wyławianie kontekstu z masy logów tworzonych w aplikacji wielowątkowej, gdzie wiele operacji przeplata się nawzajem.

Regularnie jednak zdarza się, że interesuje nas nie tyle jedna szczególna operacje co cały cykl życia konkretnego obiektu. Kumpel, z którym pracuję nad "głównym" projektem, zaproponował stosowanie takiej, bardzo prostej, logicznej i przydatnej, konwencji:

  1:  _log.Debug("User#{0} did something", user.Id);

Albo:

  1:  _log.Debug("Order#{0} processed", order.Id);

Oczywiście to uproszczony przykład, ale wiadomo o co chodzi. Dzięki temu przetwarzając logi w poszukiwaniu wszystkich informacji o użytkowniku ID=125 wyfiltruję linijki z tekstem "User#125" i mam czego potrzebowałem.

Ostatnio w mniejszym projekcie postanowiłem przetestować jak można by to uprościć jeszcze bardziej, żeby nie klepać ręcznie nazwy typu i hasha. Rozwiązanie przychodzi do głowy samo, a obrazują je te banalne testy:

  1:  public class ObjectLogInfoTests
  2:  {
  3:      public class WithId
  4:      {
  5:          public virtual int Id { get; set; }
  6:      }
  7:  
  8:      public class WithoutId
  9:      {
 10:          public virtual int SomeOtherProperty { get; set; }
 11:      }
 12:  
 13:      [Fact]
 14:      public void should_return_type_name_with_hash_and_id_if_class_has_Id_property()
 15:      {
 16:          var o = new WithId { Id = 8 };
 17:  
 18:          string logInfo = o.LogInfo();
 19:  
 20:          Assert.Equal("WithId#8", logInfo);
 21:      }
 22:  
 23:      [Fact]
 24:      public void should_return_type_name_if_class_does_not_have_Id_property()
 25:      {
 26:          var o = new WithoutId();
 27:  
 28:          string logInfo = o.LogInfo();
 29:  
 30:          Assert.Equal("WithoutId", logInfo);
 31:      }
 32:  }

Klasa implementująca te założenia jest jeszcze "banalniejsza":

  1:  public static class ObjectLoggingExtensions
  2:  {
  3:      public static string LogInfo(this object @this)
  4:      {
  5:          Type type = @this.GetType();
  6:          var idProperty = type.GetProperty("Id");
  7:          object idValue = null;
  8:          if (idProperty != null)
  9:          {
 10:              idValue = idProperty.GetValue(@this, null);
 11:          }
 12:          if (idValue == null)
 13:          {
 14:              return type.Name;
 15:          }
 16:          else
 17:          {
 18:              return "{0}#{1}".FormatWith(type.Name, idValue);
 19:          }
 20:      }
 21:  }

Dzięki temu podany na początku przykład wygląda teraz tak:

  1:  _log.Debug("{0} did something", user.LogInfo());

Oczywiście narzuca to na system konwencje nazywania właściwości "Id", jednak w tym konkretnym przypadku nie był to problem. Bez wielkiego nakładu pracy można by rozszerzyć tą klasę aby dodatkowo szukała na przykład właściwości o nazwie "[TypeName]Id".

Początkowo przez chwilę zastanowiłem się nad tym, czy wykorzystanie refleksji jest w tym przypadku słuszne ze względów wydajnościowych, jednak… nie oszukujmy się, przy kontakcie z bazą danych takie coś nie ma absolutnie żadnego znaczenia. Testowo puściłem wykonanie podobnej instrukcji w pętli milion razy i zajęło to około sekundy.

Tak czy siak rozwiązanie się póki co sprawdza (chociaż nie pokrywa wszystkich scenariuszy w których loguję informację o obiektach).

Coraz bardziej przekonuję się do tego, że zbudowanie odpowiednich pomocników wokół konceptu "logowania" jest istotną częścią systemu. Logowanie powinno być bezbolesne, momentalne i naturalne. Im mniej klawiszy trzeba wcisnąć do uzyskania informacji w logu – tym lepiej.

Zobacz również