Results 1 to 10 of 10

Thread: Automating error logging

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Automating error logging

    Hi,

    I am trying to create a class library that I can re-use in various projects. The library should handle error logging. It's nothing too special, I just want to log various things such as the exception/message, time of occurrence, some system information such as computer name, username, OS version, etc, and finally the application that the error occured in. I'd then write each entry to an XML file, and later I can create a 'logfile viewer' that reads the xml file and displays the errors in an easy to read fashion.

    Anyway, the goal here is that I just have a class LogManager that I have to 'configure' before use in every application I want to use it in. After that I just call some shared methods (WriteError, WriteMessage, etc) and they would be written to the XML file, along with the application / system information.

    This configuring should handle various things such as:
    • Look up system information (OS, 64-bit or not, username, computername, etc)
    • Look up application information (app name, version, etc)
    • Look up the 'common app data' folder for that app and create the XML file there if it does not exist yet


    All these things should happen automatically. I should not have to pass all the information to the LogManager class, as that would defeat the purpose of having a shared library (it would be less work to just do it separately for every project).


    My problem now is as follows... The things I need to look up are pretty easy to find:
    • Computername: My.Computer.Name
    • Username: My.User.Name
    • OS: Environment.OSVersion
    • App name: My.Application.ProductName (I think)
    • App version: My.Application.Version
    • Common app data path: System.Windows.Forms.Application.CommonAppDataPath


    However, I cannot use all of these in my separate class library, because they would return information about the class library, and not the project I am trying to log errors for... For example I guess the app name and version would return the name and version of the logging library, and the common app data path would be the folder for the logging library, not the folder for the project I'm logging for.


    So, short of supplying these details manually in each separate project (which would defeat the purpose), what are my options here? Can my logging library somehow figure out which application is using it..? I guess not as that would be a circular reference...

    So how can I solve this problem?
    Thanks!

  2. #2
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Automating error logging

    Dig into the System.Reflection.Assembly class. The My namespaces and many other members are just shortcuts into that anyway. (That's actually an oversimplification; Application.ProductName goes to great lengths to approximate a name if one doesn't exist, but we'll assume you're filling out your assembly metadata and don't have to worry about it like Microsoft did.)

    The key difference between what they do and what your code wants to do comes down to picking which assembly to use. Here's the choices you have for getting an assembly:
    • Assembly.GetCallingAssembly() gets the assembly that is calling the current method.
    • Assembly.GetEntryAssembly() gets "the process executable in the default application domain".
    • Assembly.GetExecutingAssembly() gets the assembly that contains the current line of code.


    GetExecutingAssembly() is definitely the wrong one; it will always return the log manager's assembly. GetCallingAssembly() could possibly work, but not if the log manager calls the code that fetches the assembly (documentation also points out optimization can mess this up.) That leaves GetEntryAssembly(). I don't understand what the documentation means with confidence, but it sounds like it suggests it gets the assembly that has the entry point of the program that's running. More often than not that should be your application. Interestingly enough, this is the method that the MS code uses, so you might want to check with Microsoft's code before wasting time writing your own.

    If it turns out GetEntryAssembly() isn't reliable, you could have the application call GetExecutingAssembly() and pass the result to your log manager; then the log manager would get the right assembly.

    If you do have to write your own, once you get the right assembly it's just a matter of looking for the right information. Surprisingly enough the version, product name, etc. are attributes on the assembly. You may not know this because by default MS hides the file with the attributes; for the most part the philosophy has a patronizing "don't show VB developers something advanced or their head might explode" feel to it. In solution explorer, push the "Show all files" button and look for "AssemblyInfo.vb" under "My Project". Here's some (untested) code that (probably) gets the version:
    Code:
    Dim assem = Assembly.GetEntryAssembly()
    
    Dim versionType As Type = GetType(AssemblyVersionAttribute)
    Dim versionAttribute = CType(assem.GetCustomAttributes(versionType, False).First())
    Dim version As String = versionAttribute.Version
    Of course, you'll have to make sure your applications provide values for these attributes, but that's a decent practice anyway.

  3. #3

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: Automating error logging

    Thanks, that's definitely helpful.

    How about the common app data path? As far as I know that's the best place to store a log file, but I cannot get to it from the Log project since I won't know which application is using it. I could always pass the path along but that's only a last resort, I much prefer it to happen automatically.


    EDIT
    I think I might be able to get it by looking at how MS implemented it via Reflector. I can see this:
    Code:
    Public Shared ReadOnly Property CommonAppDataPath As String
        Get
            Try 
                If ApplicationDeployment.IsNetworkDeployed Then
                    Dim data As String = TryCast(AppDomain.CurrentDomain.GetData("DataDirectory"),String)
                    If (Not data Is Nothing) Then
                        Return data
                    End If
                End If
            Catch exception As Exception
                If ClientUtils.IsSecurityOrCriticalException(exception) Then
                    Throw
                End If
            End Try
            Return Application.GetDataPath(Environment.GetFolderPath(SpecialFolder.CommonApplicationData))
        End Get
    End Property
    This is the System.Windows.Forms.Application.CommonAppDataPath property. Ignoring all but the last line it calls Application.GetDataPath, which seems to just build the path from the assembly information you just told me how to get:
    Code:
    Private Shared Function GetDataPath(ByVal basePath As String) As String
        Dim format As String = "{0}\{1}\{2}\{3}"
        Dim companyName As String = Application.CompanyName
        Dim productName As String = Application.ProductName
        Dim productVersion As String = Application.ProductVersion
        Dim path As String = String.Format(CultureInfo.CurrentCulture, format, New Object() { basePath, companyName, productName, productVersion })
        SyncLock Application.internalSyncObject
            If Not Directory.Exists(path) Then
                Directory.CreateDirectory(path)
            End If
        End SyncLock
        Return path
    End Function
    So that should work

  4. #4
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Automating error logging

    Yeah, the common app data path isn't a standard so much as a convention. You start with a path you can get from Environment.GetSpecialFolder(), then tack company name, product name, and version onto it. Keep in mind that's just a convention. When I'm writing software for myself I tend to omit the company name. I don't know how general you're trying to make this but you might want to leave a way for someone to specify the common app data path if you want to support all applications.

  5. #5
    PowerPoster SJWhiteley's Avatar
    Join Date
    Feb 2009
    Location
    South of the Mason-Dixon Line
    Posts
    2,256

    Re: Automating error logging

    Just curious (actually, very curious) how are you capturing the errors? Through standard exception handlers? How do you (if you do) capture unhandled exceptions?
    "Ok, my response to that is pending a Google search" - Bucky Katt.
    "There are two types of people in the world: Those who can extrapolate from incomplete data sets." - Unk.
    "Before you can 'think outside the box' you need to understand where the box is."

  6. #6

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: Automating error logging

    The usual way. If I can 'control' the error I just write it as a warning (folder doesn't exist for example), if an exception occurs (try/catch block) somewhere out of my control I write it as an error, and for unhandled exceptions I plan on using the application event that exists for it (though I've never gone that far). It's not so much about handling the errors but just a convenient way to log them. Now I just write a separate logging part for every application that needs it, handling the file IO manually each time, but I figured it would be easier to make a library that can handle all the pre-configuration stuff automatically (such as determining the log file path, creating it, writing the log entries (anything beside the error message), etc).

  7. #7

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: Automating error logging

    Quote Originally Posted by Sitten Spynne View Post
    Dig into the System.Reflection.Assembly class. The My namespaces and many other members are just shortcuts into that anyway. (That's actually an oversimplification; Application.ProductName goes to great lengths to approximate a name if one doesn't exist, but we'll assume you're filling out your assembly metadata and don't have to worry about it like Microsoft did.)

    The key difference between what they do and what your code wants to do comes down to picking which assembly to use. Here's the choices you have for getting an assembly:
    • Assembly.GetCallingAssembly() gets the assembly that is calling the current method.
    • Assembly.GetEntryAssembly() gets "the process executable in the default application domain".
    • Assembly.GetExecutingAssembly() gets the assembly that contains the current line of code.


    GetExecutingAssembly() is definitely the wrong one; it will always return the log manager's assembly. GetCallingAssembly() could possibly work, but not if the log manager calls the code that fetches the assembly (documentation also points out optimization can mess this up.) That leaves GetEntryAssembly(). I don't understand what the documentation means with confidence, but it sounds like it suggests it gets the assembly that has the entry point of the program that's running. More often than not that should be your application. Interestingly enough, this is the method that the MS code uses, so you might want to check with Microsoft's code before wasting time writing your own.

    If it turns out GetEntryAssembly() isn't reliable, you could have the application call GetExecutingAssembly() and pass the result to your log manager; then the log manager would get the right assembly.

    If you do have to write your own, once you get the right assembly it's just a matter of looking for the right information. Surprisingly enough the version, product name, etc. are attributes on the assembly. You may not know this because by default MS hides the file with the attributes; for the most part the philosophy has a patronizing "don't show VB developers something advanced or their head might explode" feel to it. In solution explorer, push the "Show all files" button and look for "AssemblyInfo.vb" under "My Project". Here's some (untested) code that (probably) gets the version:
    Code:
    Dim assem = Assembly.GetEntryAssembly()
    
    Dim versionType As Type = GetType(AssemblyVersionAttribute)
    Dim versionAttribute = CType(assem.GetCustomAttributes(versionType, False).First())
    Dim version As String = versionAttribute.Version
    Of course, you'll have to make sure your applications provide values for these attributes, but that's a decent practice anyway.
    Just wanted to get back to this. I think I understand the procedure, and it indeed works fine when I want to get the application title, but it does not work for the version (the example you posted). That's a bit odd... The AssemblyVersion attribute is there in the AssemblyInfo.vb file, just beneath the AssemblyTitle attribute (which it can find)...

    I have these functions:
    vb.net Code:
    1. Private Shared Function GetApplicationTitle() As String
    2.         Dim titleType = GetType(AssemblyTitleAttribute)
    3.         Dim attribute = _Assembly.GetCustomAttributes(titleType, False).FirstOrDefault()
    4.         If attribute IsNot Nothing Then
    5.             Dim titleAttribute = CType(attribute, AssemblyTitleAttribute)
    6.             Return titleAttribute.Title
    7.         End If
    8.         Return String.Empty
    9.     End Function
    10.  
    11.     Private Shared Function GetApplicationVersion() As String
    12.         Dim versionType = GetType(AssemblyVersionAttribute)
    13.         Dim attribute = _Assembly.GetCustomAttributes(versionType, False).FirstOrDefault()
    14.         If attribute IsNot Nothing Then
    15.             Dim versionAttribute = CType(attribute, AssemblyVersionAttribute)
    16.             Return versionAttribute.Version
    17.         End If
    18.         Return String.Empty
    19.     End Function
    For the title, it returns the right title. For the version, 'attribute' is Nothing, so it returns empty each time.

    The AssemblyInfo.vb file:
    vb.net Code:
    1. Imports System
    2. Imports System.Reflection
    3. Imports System.Runtime.InteropServices
    4.  
    5. ' General Information about an assembly is controlled through the following
    6. ' set of attributes. Change these attribute values to modify the information
    7. ' associated with an assembly.
    8.  
    9. ' Review the values of the assembly attributes
    10.  
    11. <Assembly: AssemblyTitle("LogManagerTest")>
    12. <Assembly: AssemblyDescription("")>
    13. <Assembly: AssemblyCompany("NickThissen")>
    14. <Assembly: AssemblyProduct("LogManagerTest")>
    15. <Assembly: AssemblyCopyright("Copyright © Microsoft 2011")>
    16. <Assembly: AssemblyTrademark("")>
    17.  
    18. <Assembly: ComVisible(False)>
    19.  
    20. 'The following GUID is for the ID of the typelib if this project is exposed to COM
    21. <Assembly: Guid("f6cdd100-865f-4258-8926-57604e08898f")>
    22.  
    23. ' Version information for an assembly consists of the following four values:
    24. '
    25. '      Major Version
    26. '      Minor Version
    27. '      Build Number
    28. '      Revision
    29. '
    30. ' You can specify all the values or you can default the Build and Revision Numbers
    31. ' by using the '*' as shown below:
    32. ' <Assembly: AssemblyVersion("1.0.*")>
    33.  
    34. <Assembly: AssemblyVersion("1.0.0.0")>
    35. <Assembly: AssemblyFileVersion("1.0.0.0")>

    What am I doing wrong?


    Besides this the logging now works fine by the way

    On to the next problem, I guess I will make a thread for that tomorrow (the gist of it: I am going to make a log file reader that displays the errors in a neat way. But what if I decide to change the log file structure along the way? The log file will contain old entries in the old style mixed with new entries in the new style. No way my reader can handle that correctly...)

  8. #8
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Automating error logging

    The comments in the documentation indicate this attribute is converted into part of the assembly's name and not part of the assembly's metadata. Weird. It does suggest using the GetName() method to get the AssemblyName, which includes a Version property:
    Code:
    Public Function GetVersion() As Version
        Dim assem = Assembly.GetEntryAssembly()
    
        Dim name = assem.GetName()
        Return name.Version
    End Function
    That seems to work.

  9. #9
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,714

    Re: Automating error logging

    I have developed code that for the handles most of your requirements if not all that is housed in a DLL, uses Reflection to get the parent executables information is configured via an XML configuration file and can write data to plain text or XML so that it can be viewed in a DataGridView. My viewer when open goes to the last known exception. Has a custom error dialog with a developer or user interface meaning when a developer see the dialog they see extended info while users see limited information and in either case write to a log file first. Has options to send email messages too.

    The only thing holding me back from putting it up is having docs to backup what this is all about, advantages, drawbacks, customization.

    Any ways the above was created in VS2003, ported to VS2005, ported and enhanced for VS2008 and simply used in Vs2010 without any changes. Currently used in roughly 10-15 business solutions where the only problems have been developers not configuring it correctly in the XML configuration file.



    Primer
    http://www.vbforums.com/showthread.php?t=637926

  10. #10
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,714

    Re: Automating error logging

    The attached projects, one a windows form and the other a DLL demonstrates the DLL retrieving information from the parent assembly.

    Displays the following to the console from the DLL
    Parent name, path, version and build date

    Reads an XML sample exception file using the parent assemblies paths via XDocument and LINQ

    Hope this assist with your project.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width