Global olarak benzersiz mesaj kimlikleri kullanarak kodları erişilebilir hale getirme


39

Bir hatayı bulmak için yaygın bir kalıp bu betiği izler:

  1. Garipliğe dikkat edin, örneğin çıktı yok veya bir asma programı.
  2. İlgili mesajı log veya program çıktısında bulun, örneğin, "Foo bulunamadı". (Aşağıdakiler yalnızca bu hatayı bulmak için atılan yolsa geçerlidir. Bir yığın izlemesi veya başka bir hata ayıklama bilgisi varsa, bu başka bir hikayedir.)
  3. Mesajın yazdırıldığı kodu bulun.
  4. Foo'nun fotoğrafa ilk girdiği yer (veya girmesi gerekir) ile iletinin yazdırıldığı yer arasındaki kodu hata ayıklayın.

Bu üçüncü adım, hata ayıklama işleminin genellikle durma noktasına geldiği yerdir; çünkü kodda "Foo bulunamadı" (veya şablonlu bir dize Could not find {name}) basılan birçok yer vardır. Aslında, birkaç kez heceleme hatası , asıl konumu diğerlerinden çok daha hızlı bulmama yardımcı oldu - bu, tüm sistemde ve genellikle dünya genelinde mesajı benzersiz bir hale getirerek, ilgili bir arama motorunun derhal çarpmasına neden oldu.

Bunun açık bir sonucu, kodda genel olarak benzersiz mesaj kimliklerini kullanmamız, onu mesaj dizisinin bir parçası olarak kodlamamıza ve muhtemelen her bir kimliğin kod bazında tek bir oluşumunun olduğunu doğrulamamız gerektiğidir. Sürdürülebilirlik açısından, bu topluluk bu yaklaşımın en önemli avantajları ve dezavantajları nelerdir ve bunu nasıl uygulayacaksınız ya da başka türlü uygulamanın asla gerekli olmayacağından emin olmalısınız (yazılımın her zaman hataları olacağını varsayarak)?


54
Bunun yerine yığın izlerinizi kullanın. Yığın izi, yalnızca hatanın tam olarak nerede oluştuğunu değil, aynı zamanda onu çağıran her işlevi çağıran her işlevi size söyleyecektir. Gerekirse, bir istisna olduğunda tüm izlemeyi günlüğe yaz. İstisnasız bir dilde çalışıyorsanız, C gibi, bu farklı bir hikaye.
Robert Harvey,

6
@ l0b0 ifadeler hakkında küçük bir tavsiye. "Bu topluluk ne düşünüyor ... artılarını ve eksilerini" çok geniş olarak görülebilen ifadelerdir. Bu, "iyi subjektif" sorulara izin veren bir site ve bu tür sorulara izin vermenin karşılığında, OP'nin sizden anlamlı bir fikir birliğine yönelik yorumları ve cevapları "kısma" çalışmalarını yapması beklenir.
18'de

@ rwong Teşekkürler! Bir forumda daha iyi sorulmuş olsa da, sorunun çok iyi ve yerinde bir cevap aldığını hissediyorum. JohnWu'nun açıklayıcı cevabını okuduktan sonra RobertHarvey'in yorumuna cevabımı geri çektim, belki de neye atıfta bulunuyorsanız. Değilse, herhangi bir özel çoban ipucunuz var mı?
l0b0

1
Mesajlarım "bar () çağrısı sırasında Foo bulunamadı" gibi görünüyor. Sorun çözüldü. Omuz silkme. Dezavantajı müşteriler tarafından görülmesi biraz sızan bir durumdur, ancak hata mesajlarının detayını yine de onlardan gizlemeye meyilliyiz, bu da sadece bazı fonksiyon adlarını görebilecekleri bir maymun veremeyen sistem yöneticilerinin kullanımına açık hale getirme eğilimindedir. Bunu başaramazsanız, evet, güzel bir küçük benzersiz kimlik / kod hile yapacak.
Monica

1
Bu, bir müşteri sizi aradığında ve bilgisayarları İngilizce olarak çalışmadığında ÇOK yararlıdır! Bugünlerde e-posta ve kayıt dosyalarımız olduğu için bir sorundan çok daha az .....
Ian

Yanıtlar:


12

Genel olarak bu geçerli ve değerli bir stratejidir. İşte bazı düşünceler.

Bu strateji aynı zamanda, tüm bu bilgiler birleştirildiğinde, yürütme izini "üçgenlemeye" yardımcı olan ve bir sorun gidericinin kullanıcının / uygulamanın neyi başarmaya çalıştığını ve gerçekte ne olduğunu anlamalarına olanak tanıdığı anlamında "telemetri" olarak da bilinir. .

Toplanması gereken bazı önemli veriler (hepimizin bildiği gibi):

  • Kodun yeri, yani çağrı yığını ve yaklaşık kod satırı
    • İşlevler uygun bir şekilde küçük birimlere ayrılırsa, "Yaklaşık kod satırı" gerekli değildir.
  • Fonksiyonun başarısı / başarısızlığı ile ilgili olan herhangi bir veri parçası
  • İnsan kullanıcısı / harici ajan / API kullanıcısının neyi başarmaya çalıştığını tespit edebilen üst düzey bir "komut".
    • Buradaki fikir bir yazılımın bir yerden gelen komutları kabul etmesi ve işlemesidir.
    • Bu işlem sırasında, onlarca yüzlerce ila binlerce işlev çağrısı yapılmış olabilir.
    • Bu süreç boyunca üretilen herhangi bir telemetri, bu süreci tetikleyen en yüksek seviyedeki komuta kadar izlenebilir olmasını istiyoruz.
    • Web tabanlı sistemler için, orijinal HTTP talebi ve verileri, bu "üst seviye istek bilgisine" bir örnek olacaktır.
    • GUI sistemlerinde, bir şeyi tıklayan kullanıcı bu açıklamaya uygun olacaktır.

Çoğu zaman, geleneksel günlükleme yaklaşımları, düşük düzeyli bir günlük iletisini, onu tetikleyen en üst düzey komuta geri izlemedeki başarısızlık nedeniyle yetersiz kalmaktadır. Bir yığın izlemesi, bazen bu komutu tanımlamak için gerekli olan ayrıntıları (verileri) değil, yalnızca en üst düzey komutun kullanılmasına yardımcı olan daha üst düzey işlevlerin adlarını alır.

Normalde yazılım bu tür izlenebilirlik şartlarını yerine getirmek için yazılmamıştır. Bu, düşük seviyeli mesajın yüksek seviyeli komutla ilişkilendirilmesini zorlaştırır. Sorun, özellikle çok sayıda istek ve yanıtın üst üste gelebileceği ve çok sayıda istek ve yanıtın üst üste gelebileceği ve işlemlerin orijinal istek alan iş parçacığından farklı bir iş parçacığına yüklenebileceği serbest çok dişli sistemlerde daha kötüdür.

Bu nedenle, telemetriden en yüksek değeri elde etmek için, genel yazılım mimarisinde değişiklik yapılması gerekecektir. Bir çok "arabirim" argümanını kabul edip yaymak için çoğu arabirim ve işlev çağrısının değiştirilmesi gerekir.

Yardımcı program işlevlerinin bile bir "izleyici" argümanı eklemesi gerekecek, böylece başarısız olursa, günlük mesajı kendisinin belirli bir üst düzey komutla ilişkilendirilmesine izin verecektir.

Telemetri izlemesini zorlaştıracak bir diğer başarısızlık, nesne referanslarının (boş işaretçiler veya referanslar) olmamasıdır. Çok önemli bir veri parçası eksik olduğunda, başarısızlık için yararlı bir şey bildirmek imkansız olabilir.

Günlük mesajlarını yazarken:

  • Bazı yazılım projeleri, yalnızca yöneticilere yönelik günlük mesajları için bile yerelleştirme (yabancı dilde çeviri) gerektirebilir.
  • Bazı yazılım projeleri, hassas verilerle hassas olmayan veriler arasında, günlük tutma amacıyla bile açık bir şekilde ayrılmaya ihtiyaç duyabilir ve yöneticilerin, belirli hassas verileri yanlışlıkla görme şansına sahip olamayabilir.
  • Hata mesajını karıştırmaya çalışmayın. Bu, müşterilerin güvenini baltalayacaktır. Müşterilerin yöneticileri bu kayıtları okumayı ve anlamlandırmayı beklemektedir. Müşterilerin yöneticilerinden gizlenmesi gereken bazı özel sırlar olduğunu hissettirmeyin.
  • Müşterilerin bir telemetri kütüğü parçası getirmelerini ve teknik destek personelinizi ızgara yapmalarını bekleyin. Bilmeyi bekliyorlar. Telemetri kaydını doğru şekilde açıklamak için teknik destek personelinizi eğitin.

1
Gerçekten de, AOP, öncelikle, bu sorunu çözme konusundaki doğal kabiliyetini - ilgili her çağrıya Tracer ekleyerek - kod tabanına asgari düzeyde istila ile lanse etti.
Piskopos,

Ayrıca, başarısızlığı "neden" yerine "neden" ve "nasıl düzeltileceği" olarak nitelemek için önemli olan "günlük mesajları yazma" listesine de eklerdim.
Piskopos,

58

Kodunuzdaki yüzlerce yerde kullanılan önemsiz bir yardımcı işlev olduğunu hayal edin:

decimal Inverse(decimal input)
{
    return 1 / input;
}

İstediğin gibi yaparsak, yazabiliriz.

decimal Inverse(decimal input)
{
    try 
    {
        return 1 / input;
    }
    catch(Exception ex)
    {
        log.Write("Error 27349262 occurred.");
    }
}

Oluşabilecek bir hata, girişin sıfır olması; bu, sıfır istisna ile bölünmeye neden olur.

Diyelim ki çıktılarınızda veya kayıtlarınızda 27349262'yi görüyorsunuz. Sıfır değerini geçen kodu nerede buluyorsunuz? Unutma, işlev - benzersiz kimliğiyle - yüzlerce yerde kullanılır. Öyleyse sıfıra bölünmenin gerçekleştiğini bilirken, onun kim olduğu hakkında hiçbir fikriniz yok 0.

Bana mesaj kimliklerini kaydetmeyi zahmete sokacaksanız, yığın izlemesini kaydedebilirsiniz.

Yığın izinin ayrıntılarıyla ilgili sizi rahatsız eden şey varsa, çalışma zamanının size verdiği şekilde bir dize olarak boşaltmanız gerekmez. Özelleştirebilirsiniz. Örneğin, yalnızca nseviyelere giden kısaltılmış bir yığın izlemesi istiyorsanız, bunun gibi bir şey yazabilirsiniz (c # kullanıyorsanız):

static class ExtensionMethods
{
    public static string LimitedStackTrace(this Exception input, int layers)
    {
        return string.Join
        (
            ">",
            new StackTrace(input)
                .GetFrames()
                .Take(layers)
                .Select
                (
                    f => f.GetMethod()
                )
                .Select
                (
                    m => string.Format
                    (
                        "{0}.{1}", 
                        m.DeclaringType, 
                        m.Name
                    )
                )
                .Reverse()
        );
    }
}

Ve bu şekilde kullanın:

public class Haystack
{
    public static void Needle()
    {
        throw new Exception("ZOMG WHERE DID I GO WRONG???!");
    }

    private static void Test()
    {
        Needle();
    }

    public static void Main()
    {
        try
        {
            Test();
        }
        catch(System.Exception e)
        {
            //Get 3 levels of stack trace
            Console.WriteLine
            (
                "Error '{0}' at {1}", 
                e.Message, 
                e.LimitedStackTrace(3)
            );  
        }
    }
}

Çıktı:

Error 'ZOMG WHERE DID I GO WRONG???!' at Haystack.Main>Haystack.Test>Haystack.Needle

Mesaj kimliğini korumaktan daha kolay ve daha esnek olabilir.

Kodumu DotNetFiddle'dan çalmak


32
Hmm Sanırım açıkça net bir şekilde anlamadım. Kod yeri itibariyle benzersiz Robert olduklarını biliyorum . Kod yolu başına benzersiz değillerdir . Konumu bilmek genellikle işe yaramazdır, örneğin asıl sorun bir girişin doğru şekilde ayarlanmamış olması. Dilimi vurgulamak için biraz değiştirdim.
John Wu

1
İkinize de iyi puanlar. Duruma bağlı olarak bir anlaşma kırıcı olabilecek ya da olmayabilecek yığın izleri ile ilgili farklı bir sorun var: Boyutları, özellikle bazı diller gibi kısaltılmış bir sürüm yerine tüm yığın izlemesini dahil etmek istiyorsanız, mesajları değiştirmelerine neden olabilir varsayılan olarak yapın. Belki bir alternatif ayrı olarak bir yığın izleme günlüğü yazmak ve uygulama çıktısına o günlüğe numaralandırılmış indeksler eklemek olabilir.
l0b0

12
Bunlardan çoğunu alıyorsanız, G / Ç'nizi su basmanızdan endişe duyuyorsanız, ciddi bir sorun var. Yoksa sadece cimri misin? Gerçek performans hedefi büyük olasılıkla istifin çözülmesidir.
John Wu

9
Bir 3.5 diskete günlükleri yazmanız durumunda yığın izlerini kısaltmak için bir çözümle düzenlenmiş;)
John Wu

7
@JohnWu Ve ayrıca [...] adresindeki "IOException 'Dosya Bulunamadı' ifadesini de unutmayın; bu, arama yığınının elli katmanını söyler ancak tam olarak hangi kanlı dosyanın bulunamadığını söylemez.
Joker_vD

6

SAP NetWeaver bunu yıllardır yapıyor.

Tipik SAP ERP sistemi olan devasa koddaki hataları giderirken değerli bir araç olduğunu kanıtladı.

Hata mesajları, her bir mesajın mesaj sınıfı ve mesaj numarası ile tanımlandığı merkezi bir depoda yönetilir.

Bir hata mesajı çıkarmak istediğinizde, sadece sınıf, sayı, önem derecesi ve mesaja özgü değişkenleri belirtirsiniz. İletinin metin gösterimi çalışma zamanında oluşturulur. Mesaj sınıfını ve numarasını genellikle mesajların göründüğü herhangi bir bağlamda görürsünüz. Bunun birkaç zarif etkisi var:

  • Herhangi bir kod satırını ABAP kod tabanında, belirli bir hata mesajı oluşturan otomatik olarak bulabilirsiniz.

  • Belirli bir hata mesajı üretildiğinde tetikleyen dinamik hata ayıklayıcı kesme noktalarını ayarlayabilirsiniz.

  • SAP bilgi bankası makalelerindeki hataları arayabilir ve "Foo bulunamadı" ifadesinden daha alakalı arama sonuçları elde edebilirsiniz.

  • Mesajların metin gösterimleri çevrilebilir. Böylece, stringler yerine mesajların kullanımını teşvik ederek i18n yeteneklerini de elde edersiniz.

Mesaj numarasına sahip bir hata açılır penceresi örneği:

HATA1

Hata havuzunda bu hatayı ararken:

ERROR2

Kod tabanında bulun:

ERROR3

Ancak dezavantajları var. Gördüğünüz gibi, bu kod satırları artık kendi kendini belgelendirmiyor. Kaynak kodu okuduğunuzda ve MESSAGEyukarıdaki ekran görüntüsündeki gibi bir ifade gördüğünüzde , bağlamdan gerçekten ne anlama geldiğini görebilirsiniz. Ayrıca, bazen insanlar çalışma zamanında ileti sınıfını ve numarasını alan özel hata işleyicileri uygular. Bu durumda, hata otomatik olarak bulunamaz veya hatanın gerçekleştiği yerde bulunamaz. İlk sorunun geçici çözümü, okuyucuya mesajın ne anlama geldiğini söyleyen her zaman kaynak kodda bir yorum eklemeyi alışkanlık haline getirmektir. İkinci mesaj, otomatik mesaj aramanın çalıştığından emin olmak için bir miktar ölü kod ekleyerek çözülür. Örnek:

" Do not use special characters
my_custom_error_handler->post_error( class = 'EU' number = '271').
IF 1 = 2.
   MESSAGE e271(eu).
ENDIF.    

Ancak bunun mümkün olmadığı bazı durumlar var. Örneğin, işletme kuralları ihlal edildiğinde görünecek hata mesajlarını yapılandırabileceğiniz UI tabanlı bazı iş süreci modelleme araçları vardır. Bu araçların uygulanması tamamen veriye dayalıdır, bu nedenle bu hatalar kullanılan listede görünmez. Bu, bir hatanın nedenini bulmaya çalışırken kullanılan listede çok fazla güvenmek anlamına gelir, kırmızı bir ringa balığı olabilir.


Mesaj katalogları da bir süredir GNU / Linux - ve UNIX'in genel olarak bir POSIX standardı - parçası olmuştur.
Piskopos,

@bishop Genelde özellikle POSIX sistemleri için programlama yapmıyorum, bu yüzden ona aşina değilim. Belki POSIX mesaj kataloglarını ve OP'nin uygulanmasından neler öğrenebileceğini açıklayan başka bir cevap gönderebilirsiniz.
Philipp

3
Bunu dişilerde yapan bir projenin parçasıydım. Karşılaştığımız bir sorun, diğer her şeyle birlikte veritabanına "veritabanına bağlanılamıyor" için insan mesajını vermemizdi.
JimmyJames

5

Bu yaklaşımla ilgili sorun, daha ayrıntılı bir günlüklemeye yol açmasıdır. Bunların% 99,9999'u asla bakmayacak.

Bunun yerine, sürecin başlangıcında devleti yakalamanızı ve işlemin başarısını / başarısızlığını öneriyorum.

Bu, hatayı yerel olarak yeniden oluşturmanıza, kod içinde ilerlemenize ve günlüklerinizi işlem başına iki yere sınırlamanıza olanak tanır. Örneğin.

OrderPlaced {id:xyz; ...order data..}
OrderPlaced {id:xyz; ...Fail, ErrorMessage..}

Şimdi, hatayı yeniden üretmek için hata ayıklayıcımdaki kodu kullanarak ve düzeltmeyi onaylamak için yeni bir birim sınaması yazarak, dev makinemdeki aynı durumu kullanabilirim.

Ek olarak, gerekirse yalnızca günlüğe kaydetme hatalarını günlüğe kaydederek veya durumu başka bir yerde tutarak daha fazla günlüğe kaydetmekten kaçınabilirim (veritabanı? İleti sırası?)

Açıkçası hassas verilerin kaydedilmesi konusunda daha dikkatli olmamız gerekiyor. Dolayısıyla, çözümünüz ileti sıralarını veya olay deposu düzenini kullanıyorsa bu özellikle işe yarar. Günlük sadece "xyz Mesaj Başarısız" mesajı


Hassas verileri bir sıraya koymak hala günlüğe kaydetmektedir. Bu, kötü niyetli bir şekilde, tıpkı DB'de bir tür şifreleme biçimi olmadan hassas girişleri depolamak gibi.
jpmc26

sisteminizde kuyruklar veya db'ler tükenirse, veriler zaten oradadır ve güvenlik de gerekir. Çok fazla günlüğe kaydetmek yalnızca kötüdür çünkü günlük, güvenlik denetimlerinizin dışına düşme eğilimindedir.
Ewan

Doğru, ama mesele bu. Bu tavsiye edilmez çünkü veriler orada kalıcı olarak ve genellikle tamamen açık metin olarak kalıyor. Hassas veriler için, riske girmemek ve süreyi nereye sakladığınızı en aza indirgemek ve ardından onu nasıl sakladığınız konusunda çok dikkatli ve dikkatli olmak daha iyidir.
jpmc26

Geleneksel olarak kalıcı çünkü bir dosyaya yazıyorsunuz. Ancak bir hata sırası geçicidir.
Ewan

Bunun muhtemelen sıranın uygulanmasına (ve muhtemelen ayarlarına) bağlı olduğunu söyleyebilirim. Sadece herhangi bir sıraya atamaz ve güvende olmasını bekleyemezsin. Ve sıra tüketildikten sonra ne olur? Kayıtlar, birinin görüntüleyebileceği bir yerde olmalıdır. Ayrıca, bu geçici bir saldırı bile yapmak istemeyeceğim ekstra bir saldırı vektörü. Bir saldırının oraya giden hassas veriler olduğunu öğrenirse, en yeni kayıtlar bile değerli olabilir. Ve sonra birinin anahtarı bilmemesi ve çevirmemesi riski vardır, böylece diske de giriş yapmaya başlar. Sadece bir solucan konservesi.
jpmc26

1

Günlüğe kaydetmenin buna devam etmenin bir yolu olmadığını, bunun yerine bu durumun istisnai olarak kabul edildiğini (programınızı kilitlediğini) ve bir istisna atılması gerektiğini öneriyorum. Diyelim ki kodunuz:

public Foo GetFoo() {

     //Expecting that this should never by null.
     var aFoo = ....;

     if (aFoo == null) Log("Could not find Foo.");

     return aFoo;
}

Sesli arama kodu, Foo'nun mevcut olmadığı ve potansiyel olarak aşağıdakileri yapabileceğiniz gerçeğiyle başa çıkmak için ayarlanmamış gibi görünüyor:

public Foo GetFooById(int id) {
     var aFoo = ....;

     if (aFoo == null) throw new ApplicationException("Could not find Foo for ID: " + id);

     return aFoo;
}

Ve bu hata ayıklamaya yardımcı olmak için kullanılabilecek istisna dışında bir yığın izini döndürür.

Alternatif olarak, Foo'nun geri alındığında null olmasını beklersek ve sorun olmazsa, arama sitelerini düzeltmemiz gerekir:

void DoSomeFoo(Foo aFoo) {

    //Guard checks on your input - complete with stack trace!
    if (aFoo == null) throw new ArgumentNullException(nameof(aFoo));

    ... operations on Foo...
}

Yazılımınızın beklenmedik koşullar altında 'garip bir şekilde' askıda kalması veya hareket etmesi gerçeği bana yanlış geliyor - eğer bir Foo'ya ihtiyacınız varsa ve orada olmayışı idare edemiyorsanız, o zaman bir yol boyunca ilerlemeye çalışmaktan ziyade çökmek daha iyi görünüyor sisteminizi bozmak.


0

Doğru bir şekilde günlüğe kaydetme kitaplıkları, uzantı mekanizmaları sağlar; bu nedenle, bir günlük iletisinin kaynaklandığı yöntemi bilmek istiyorsanız, bunu kutudan yapabilirler. İşlem bir yığın izlemesi oluşturmayı ve günlük kitaplığından çıkana kadar geçmeyi gerektirdiğinden yürütme üzerinde bir etkisi vardır.

Bununla birlikte, kimliğinizin sizin için ne yapmasını istediğinize bağlı:

  • Kullanıcıya sağlanan hata mesajlarını günlüklerinizle ilişkilendirir misiniz?
  • Mesaj oluşturulduğunda hangi kodun yürütüldüğüne dair not verin?
  • Makine adını ve servis örneğini takip edin.
  • İş parçacığı kimliği takip edin?

Bunların tümü, uygun bir kayıt yazılımı ile kutudan yapılabilir (yani Console.WriteLine()ya da değil Debug.WriteLine()).

Şahsen, daha önemlisi, yürütme yollarını yeniden inşa edebilme yeteneğidir. Zipkin gibi araçlar başarmak için tasarlandı. Sistemdeki bir kullanıcı eyleminin davranışını izlemek için bir kimlik. Günlüklerinizi merkezi bir arama motoruna koyarak, yalnızca en uzun süren işlemleri bulamazsınız, ancak o eylem için geçerli olan günlükleri ( ELK yığını gibi ) çağırırsınız .

Her mesajla değişen Opak ID'ler çok kullanışlı değildir. Davranışları bir dizi mikro servis grubundan izlemek için kullanılan tutarlı bir kimlik ... son derece kullanışlıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.